mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-27 21:08:00 +00:00
docs: gitbooks v3 — flatten frontend/tauri-shell, kill product/, fix links, default app env to production (#1385)
This commit is contained in:
@@ -9,8 +9,8 @@ Authoritative docs for AI agents and contributors:
|
||||
- **[`gitbooks/developing/architecture.md`](../../gitbooks/developing/architecture.md)** — narrative architecture, dual-socket sync.
|
||||
- **[`gitbooks/resources/design-language.md`](../../gitbooks/resources/design-language.md)** — visual language.
|
||||
- **[`gitbooks/developing/e2e-testing.md`](../../gitbooks/developing/e2e-testing.md)** — WDIO/Appium testing.
|
||||
- **[`gitbooks/developing/frontend/README.md`](../../gitbooks/developing/frontend/README.md)** — frontend.
|
||||
- **[`gitbooks/developing/tauri-shell/README.md`](../../gitbooks/developing/tauri-shell/README.md)** — Tauri shell.
|
||||
- **[`gitbooks/developing/frontend.md`](../../gitbooks/developing/frontend.md)** — frontend.
|
||||
- **[`gitbooks/developing/tauri-shell.md`](../../gitbooks/developing/tauri-shell.md)** — Tauri shell.
|
||||
|
||||
## When to add a file here
|
||||
|
||||
|
||||
+12
-9
@@ -8,19 +8,22 @@
|
||||
# ---------------------------------------------------------------------------
|
||||
# App environment
|
||||
# ---------------------------------------------------------------------------
|
||||
# [optional] set to either 'production' or 'staging'
|
||||
OPENHUMAN_APP_ENV=staging
|
||||
# [optional] App environment selector: production | staging.
|
||||
# Defaults to 'production' when unset. Uncomment and set to 'staging' to point
|
||||
# at the staging backend, use the ~/.openhuman-staging workspace, etc.
|
||||
# OPENHUMAN_APP_ENV=staging
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Backend API
|
||||
# ---------------------------------------------------------------------------
|
||||
# [optional] App environment selector for default paths/URLs: production | staging
|
||||
OPENHUMAN_APP_ENV=staging
|
||||
# [required] Primary backend URL (read by Rust core and QuickJS skills sandbox)
|
||||
BACKEND_URL=https://staging-api.tinyhumans.ai
|
||||
# [required] Also read by Vite frontend (VITE_ prefix required for browser exposure)
|
||||
VITE_OPENHUMAN_APP_ENV=staging
|
||||
VITE_BACKEND_URL=https://staging-api.tinyhumans.ai
|
||||
# [optional] Primary backend URL (read by Rust core and QuickJS skills sandbox).
|
||||
# Defaults to https://api.tinyhumans.ai (production). Override here only if you
|
||||
# want a different backend (e.g. https://staging-api.tinyhumans.ai).
|
||||
# BACKEND_URL=https://api.tinyhumans.ai
|
||||
# [optional] Vite frontend mirrors — only required if you set OPENHUMAN_APP_ENV
|
||||
# above. Defaults are production.
|
||||
# VITE_OPENHUMAN_APP_ENV=staging
|
||||
# VITE_BACKEND_URL=https://staging-api.tinyhumans.ai
|
||||
# [optional] Consumer first-session UX in the desktop/web app (default off). See docs/plans/consumer-first-session-spec.md
|
||||
# VITE_CONSUMER_FIRST_SESSION=true
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
**AI-powered assistant for communities — React + Tauri v2 desktop app with a Rust core (JSON-RPC / CLI) and sandboxed QuickJS skills.**
|
||||
|
||||
This file orients contributors and coding agents. Authoritative narrative architecture: [`gitbooks/developing/architecture.md`](gitbooks/developing/architecture.md). Frontend layout: [`gitbooks/developing/frontend/README.md`](gitbooks/developing/frontend/README.md). Tauri shell: [`gitbooks/developing/tauri-shell/README.md`](gitbooks/developing/tauri-shell/README.md).
|
||||
This file orients contributors and coding agents. Authoritative narrative architecture: [`gitbooks/developing/architecture.md`](gitbooks/developing/architecture.md). Frontend layout: [`gitbooks/developing/frontend.md`](gitbooks/developing/frontend.md). Tauri shell: [`gitbooks/developing/tauri-shell.md`](gitbooks/developing/tauri-shell.md).
|
||||
|
||||
---
|
||||
|
||||
@@ -292,7 +292,7 @@ Bundled prompts live under **`src/openhuman/agent/prompts/`** at the **repositor
|
||||
|
||||
Thin desktop host: window management, daemon health bridging, **core process lifecycle** (`core_process`, `CoreProcessHandle`), and **JSON-RPC relay** to the **`openhuman`** sidecar (`core_rpc_relay`, `core_rpc`).
|
||||
|
||||
Registered IPC commands (see [`gitbooks/developing/tauri-shell/commands.md`](gitbooks/developing/tauri-shell/commands.md)) include **`greet`**, **`write_ai_config_file`**, **`ai_get_config`**, **`ai_refresh_config`**, **`core_rpc_relay`**, **window** commands, and **OpenHuman service / daemon host** helpers (`openhuman_*`).
|
||||
Registered IPC commands (see [`gitbooks/developing/tauri-shell.md`](gitbooks/developing/tauri-shell.md)) include **`greet`**, **`write_ai_config_file`**, **`ai_get_config`**, **`ai_refresh_config`**, **`core_rpc_relay`**, **window** commands, and **OpenHuman service / daemon host** helpers (`openhuman_*`).
|
||||
|
||||
Deep link plugin is registered where supported; behavior is platform-specific (see platform notes below).
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
**AI assistant for communities — React + Tauri v2 desktop app with a Rust core (JSON-RPC / CLI).**
|
||||
|
||||
Narrative architecture: [`gitbooks/developing/architecture.md`](gitbooks/developing/architecture.md). Frontend: [`gitbooks/developing/frontend/README.md`](gitbooks/developing/frontend/README.md). Tauri shell: [`gitbooks/developing/tauri-shell/README.md`](gitbooks/developing/tauri-shell/README.md). Coding-harness tool surface: [`gitbooks/developing/coding-harness.md`](gitbooks/developing/coding-harness.md).
|
||||
Narrative architecture: [`gitbooks/developing/architecture.md`](gitbooks/developing/architecture.md). Frontend: [`gitbooks/developing/frontend.md`](gitbooks/developing/frontend.md). Tauri shell: [`gitbooks/developing/tauri-shell.md`](gitbooks/developing/tauri-shell.md). Coding-harness tool surface: [`gitbooks/developing/coding-harness.md`](gitbooks/developing/coding-harness.md).
|
||||
|
||||
---
|
||||
|
||||
@@ -165,7 +165,7 @@ bash scripts/test-rust-with-mock.sh --test json_rpc_e2e
|
||||
|
||||
Thin desktop host: window management, daemon health, **core process lifecycle** (`core_process`, `CoreProcessHandle`), **JSON-RPC relay** (`core_rpc_relay`, `core_rpc`).
|
||||
|
||||
Registered IPC (see [`gitbooks/developing/tauri-shell/commands.md`](gitbooks/developing/tauri-shell/commands.md)): `greet`, `write_ai_config_file`, `ai_get_config`, `ai_refresh_config`, `core_rpc_relay`, window commands, `openhuman_*` daemon helpers.
|
||||
Registered IPC (see [`gitbooks/developing/tauri-shell.md`](gitbooks/developing/tauri-shell.md)): `greet`, `write_ai_config_file`, `ai_get_config`, `ai_refresh_config`, `core_rpc_relay`, window commands, `openhuman_*` daemon helpers.
|
||||
|
||||
### CEF child webviews — no new JS injection
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<h1 align="center">OpenHuman</h1>
|
||||
|
||||
<p align="center">
|
||||
<img src="./docs/mascot.gif" alt="The Tet" />
|
||||
<img src="./gitbooks/.gitbook/assets/demo.png" alt="The Tet" />
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
@@ -61,7 +61,7 @@ OpenHuman is an open-source agentic assistant designed to integrate with you in
|
||||
|
||||
- **[Optional local AI (via Ollama)](https://tinyhumans.gitbook.io/openhuman/features/local-ai)** — Off by default. Opt in per-workload to keep memory embeddings, summary-tree building, and background reflection loops on your machine. Chat / vision / voice stay cloud — the local path is scoped to the workloads where on-device actually pays.
|
||||
|
||||
- **[Messaging channels](https://tinyhumans.gitbook.io/openhuman/product/messaging-channels), [teams & orgs](https://tinyhumans.gitbook.io/openhuman/product/teams), [privacy & security](https://tinyhumans.gitbook.io/openhuman/product/privacy-and-security)** — Inbound/outbound across the channels you already use, shared workspaces for collaborating with an agent across a team, and workflow data that stays on device, encrypted locally, treated as yours.
|
||||
- **[Messaging channels](https://tinyhumans.gitbook.io/openhuman/features/integrations#messaging-channels)** and **[privacy & security](https://tinyhumans.gitbook.io/openhuman/features/privacy-and-security)** — Inbound/outbound across the channels you already use, with workflow data that stays on device, encrypted locally, treated as yours.
|
||||
|
||||
For contributors: [Architecture](https://tinyhumans.gitbook.io/openhuman/developing/architecture) · [Getting Set Up](https://tinyhumans.gitbook.io/openhuman/developing/getting-set-up) · [Cloud Deploy](https://tinyhumans.gitbook.io/openhuman/developing/cloud-deploy) · [`CONTRIBUTING.md`](./CONTRIBUTING.md).
|
||||
|
||||
@@ -69,17 +69,17 @@ For contributors: [Architecture](https://tinyhumans.gitbook.io/openhuman/develop
|
||||
|
||||
High-level comparison (products evolve—verify against each vendor). OpenHuman is built to **minimize vendor sprawl**, keep **workflow knowledge on-device**, and give the agent a **persistent memory** of your data — not only chat.
|
||||
|
||||
| | Claude Cowork | OpenClaw | Hermes Agent | OpenHuman |
|
||||
| --------------------- | ------------------ | ----------------- | ----------------- | ---------------------------------- |
|
||||
| **Open-source** | 🚫 Proprietary | ✅ MIT | ✅ MIT | ✅ GNU |
|
||||
| **Simple to start** | ✅ Desktop + CLI | ⚠️ Terminal-first | ⚠️ Terminal-first | ✅ Clean UI, minutes |
|
||||
| **Cost** | ⚠️ Sub + add-ons | ⚠️ BYO models | ⚠️ BYO models | ✅ One sub + TokenJuice |
|
||||
| **Memory** | ✅ Chat-scoped | ⚠️ Plugin-reliant | ✅ Self-learning | 🚀 Memory Tree + Obsidian vault |
|
||||
| **Integrations** | ⚠️ Few connectors | ⚠️ BYO | ⚠️ BYO | 🚀 118+ via OAuth |
|
||||
| **Auto-fetch** | 🚫 None | 🚫 None | 🚫 None | ✅ 5-min sync into memory |
|
||||
| **API sprawl** | 🚫 Extra keys | 🚫 BYOK | 🚫 Multi-vendor | ✅ One account |
|
||||
| **Model routing** | 🚫 Single model | ⚠️ Manual | ⚠️ Manual | ✅ Built-in |
|
||||
| **Native tools** | ✅ Code-only | ✅ Code-only | ✅ Code-only | ✅ Code + search + scraper + voice |
|
||||
| | Claude Cowork | OpenClaw | Hermes Agent | OpenHuman |
|
||||
| ------------------- | ----------------- | ----------------- | ----------------- | ---------------------------------- |
|
||||
| **Open-source** | 🚫 Proprietary | ✅ MIT | ✅ MIT | ✅ GNU |
|
||||
| **Simple to start** | ✅ Desktop + CLI | ⚠️ Terminal-first | ⚠️ Terminal-first | ✅ Clean UI, minutes |
|
||||
| **Cost** | ⚠️ Sub + add-ons | ⚠️ BYO models | ⚠️ BYO models | ✅ One sub + TokenJuice |
|
||||
| **Memory** | ✅ Chat-scoped | ⚠️ Plugin-reliant | ✅ Self-learning | 🚀 Memory Tree + Obsidian vault |
|
||||
| **Integrations** | ⚠️ Few connectors | ⚠️ BYO | ⚠️ BYO | 🚀 118+ via OAuth |
|
||||
| **Auto-fetch** | 🚫 None | 🚫 None | 🚫 None | ✅ 5-min sync into memory |
|
||||
| **API sprawl** | 🚫 Extra keys | 🚫 BYOK | 🚫 Multi-vendor | ✅ One account |
|
||||
| **Model routing** | 🚫 Single model | ⚠️ Manual | ⚠️ Manual | ✅ Built-in |
|
||||
| **Native tools** | ✅ Code-only | ✅ Code-only | ✅ Code-only | ✅ Code + search + scraper + voice |
|
||||
|
||||
# Star us on GitHub
|
||||
|
||||
|
||||
+6
-3
@@ -4,8 +4,9 @@
|
||||
#
|
||||
# Tags: [required] must be set, [optional] has a sensible default or can be blank
|
||||
|
||||
# [optional] App environment selector for default backend fallback: production | staging
|
||||
VITE_OPENHUMAN_APP_ENV=staging
|
||||
# [optional] App environment selector for default backend fallback: production | staging.
|
||||
# Defaults to 'production' when unset. Uncomment to point the dev frontend at staging.
|
||||
# VITE_OPENHUMAN_APP_ENV=staging
|
||||
|
||||
# [optional] Core RPC endpoint — build-time fallback only.
|
||||
# Runtime precedence (highest first):
|
||||
@@ -20,7 +21,9 @@ VITE_OPENHUMAN_CORE_RPC_URL=http://127.0.0.1:7788/rpc
|
||||
# Desktop builds derive `api_url` at runtime from the core via
|
||||
# `openhuman.config_resolve_api_url` after the RPC handshake succeeds, so this
|
||||
# value only matters when the app runs outside Tauri (web preview, Storybook).
|
||||
VITE_BACKEND_URL=https://staging-api.tinyhumans.ai
|
||||
# Defaults to https://api.tinyhumans.ai (production); override only when you
|
||||
# need a different backend (e.g. https://staging-api.tinyhumans.ai).
|
||||
# VITE_BACKEND_URL=https://staging-api.tinyhumans.ai
|
||||
|
||||
# [optional] Telegram bot username used for managed DM linking fallback (default: openhuman_bot)
|
||||
VITE_TELEGRAM_BOT_USERNAME=openhuman_bot
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.5 MiB |
Binary file not shown.
|
Before Width: | Height: | Size: 8.5 MiB |
Binary file not shown.
|
Before Width: | Height: | Size: 9.2 MiB |
Binary file not shown.
|
Before Width: | Height: | Size: 9.3 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.4 MiB |
+17
-17
@@ -7,21 +7,21 @@ icon: diamond
|
||||
|
||||
# Welcome to OpenHuman
|
||||
|
||||
<figure><img src=".gitbook/assets/Screenshot 2026-05-08 at 9.39.39 PM.png" alt=""><figcaption></figcaption></figure>
|
||||
<figure><img src=".gitbook/assets/demo.png" alt=""><figcaption></figcaption></figure>
|
||||
|
||||
OpenHuman is an open-source AI assistant designed to be the **memory** and **doer** for everything you do across your tools. Built on Rust + Tauri and licensed under GNU GPL3, it closes the gap between what AI models can do and what they actually know about _you_.
|
||||
|
||||
Every model in the world — all 200+ of them — shares the same fundamental limitation: they are stateless. You type a prompt, get a response, and the context evaporates. Even the ones with "memory" store a few bullet points. A few bullet points is a sticky note, not intelligence.
|
||||
Every model in the world, all 200+ of them, shares the same fundamental limitation: they are stateless. You type a prompt, get a response, and the context evaporates. Even the ones with "memory" store a few bullet points. A few bullet points is a sticky note, not intelligence.
|
||||
|
||||
OpenHuman solves this with a stack that's calmly, deliberately different:
|
||||
|
||||
**A local-first** [**Memory Tree**](features/obsidian-wiki/memory-tree.md)**.** Every source you connect — Gmail, Slack, GitHub, Notion, your own notes — flows through a deterministic pipeline: canonical Markdown, ≤3k-token chunks, scored, folded into per-source / per-topic / per-day summary trees. Stored in SQLite on your machine. No vector-soup black box.
|
||||
**A local-first** [**Memory Tree**](features/obsidian-wiki/memory-tree.md)**.** Every source you connect. Gmail, Slack, GitHub, Notion, your own notes, flows through a deterministic pipeline: canonical Markdown, ≤3k-token chunks, scored, folded into per-source / per-topic / per-day summary trees. Stored in SQLite on your machine. No vector-soup black box.
|
||||
|
||||
**An** [**Obsidian-style wiki**](features/obsidian-wiki/) **on top of it.** The same chunks the agent reasons over land as `.md` files in a vault you can open in [Obsidian](https://obsidian.md), browse, edit, and link by hand. Inspired by [Karpathy's obsidian-wiki workflow](https://x.com/karpathy/status/2039805659525644595). You can't trust a memory you can't read.
|
||||
|
||||
[**118+ third-party integrations**](features/integrations.md)**, with** [**auto-fetch**](features/auto-fetch.md)**.** One-click OAuth into Gmail, GitHub, Slack, Notion, Stripe, Calendar, Drive, Linear, Jira and more. Every five minutes, OpenHuman pulls fresh data from every active connection and folds it into the Memory Tree without you asking.
|
||||
|
||||
**An agent built for big data.** [Smart token compression (TokenJuice)](features/token-compression.md) compacts verbose tool output before it ever enters the model's context, so sweeping through your last six months of email costs single-digit dollars. [Automatic model routing](features/model-routing.md) sends each task to the right model — `hint:reasoning` to a frontier model, `hint:fast` to a cheap one, vision to vision — all under one subscription. Built-in [native tools](features/native-tools.md) cover web search, scraping, and a full filesystem/git/lint/test/grep coder loop.
|
||||
**An agent built for big data.** [Smart token compression (TokenJuice)](features/token-compression.md) compacts verbose tool output before it ever enters the model's context, so sweeping through your last six months of email costs single-digit dollars. [Automatic model routing](features/model-routing.md) sends each task to the right model, `hint:reasoning` to a frontier model, `hint:fast` to a cheap one, vision to vision, all under one subscription. Built-in [native tools](features/native-tools.md) cover web search, scraping, and a full filesystem/git/lint/test/grep coder loop.
|
||||
|
||||
[**Native voice (ElevenLabs)**](features/voice.md)**.** STT in, ElevenLabs TTS out, mascot lip-sync, and a live Google Meet agent that joins meetings, transcribes them into your Memory Tree, and can speak back into the call.
|
||||
|
||||
@@ -35,35 +35,35 @@ OpenHuman is not AGI. But it is a meaningful architectural step closer, with bet
|
||||
|
||||
OpenHuman connects to your tools, pulls from them continuously, and turns the firehose into something structured any AI can act on.
|
||||
|
||||
* It **fetches automatically** from every active integration every five minutes, so the agent already has tomorrow's context this morning.
|
||||
* It **compresses** millions of tokens of organizational noise into a deterministic Memory Tree of chunks, scores, entities, and summaries.
|
||||
* It **surfaces signals** that matter: decisions, action items, risks, sentiment shifts, and buried context you would otherwise miss.
|
||||
* It **routes intelligently**, picking the right model for each task and compacting tool output through TokenJuice so cost stays minimal even at scale.
|
||||
* It **speaks**, listens, and joins meetings — voice is a first-class surface, not an afterthought.
|
||||
* It **preserves privacy** by design. The Memory Tree's SQLite database and your Obsidian vault stay on your machine. Integration tokens are held by the OpenHuman backend, not on disk in plaintext on your laptop.
|
||||
- It **fetches automatically** from every active integration every five minutes, so the agent already has tomorrow's context this morning.
|
||||
- It **compresses** millions of tokens of organizational noise into a deterministic Memory Tree of chunks, scores, entities, and summaries.
|
||||
- It **surfaces signals** that matter: decisions, action items, risks, sentiment shifts, and buried context you would otherwise miss.
|
||||
- It **routes intelligently**, picking the right model for each task and compacting tool output through TokenJuice so cost stays minimal even at scale.
|
||||
- It **speaks**, listens, and joins meetings, voice is a first-class surface, not an afterthought.
|
||||
- It **preserves privacy** by design. The Memory Tree's SQLite database and your Obsidian vault stay on your machine. Integration tokens are held by the OpenHuman backend, not on disk in plaintext on your laptop.
|
||||
|
||||
## Who it's for
|
||||
|
||||
OpenHuman is built for people and teams who operate across many conversations and tools, and feel the cost of it.
|
||||
|
||||
* **Knowledge workers** who spend their days across 8+ applications and lose context every time they switch.
|
||||
* **Developers and power users** who want a memory and context layer that actually scales — and that they can audit by opening a folder of Markdown.
|
||||
* **High-volume communicators** who miss decisions, context, and follow-ups buried in message noise across multiple platforms.
|
||||
* **Traders and analysts** who need fast signal extraction and risk awareness across information channels.
|
||||
* **Distributed teams** who make decisions in chat but need structured follow-through in external tools.
|
||||
- **Knowledge workers** who spend their days across 8+ applications and lose context every time they switch.
|
||||
- **Developers and power users** who want a memory and context layer that actually scales, and that they can audit by opening a folder of Markdown.
|
||||
- **High-volume communicators** who miss decisions, context, and follow-ups buried in message noise across multiple platforms.
|
||||
- **Traders and analysts** who need fast signal extraction and risk awareness across information channels.
|
||||
- **Distributed teams** who make decisions in chat but need structured follow-through in external tools.
|
||||
|
||||
## What OpenHuman does not do
|
||||
|
||||
OpenHuman does not claim to be AGI. It does not take actions in your connected platforms without your explicit instruction. It does not store your raw message data on someone else's server. It does not train on your data.
|
||||
|
||||
{% hint style="info" %}
|
||||
Privacy is a core architectural decision, not a checkbox. The full privacy design is covered in [Privacy & Security](product/privacy-and-security.md).
|
||||
Privacy is a core architectural decision, not a checkbox. The full privacy design is covered in [Privacy & Security](features/privacy-and-security.md).
|
||||
{% endhint %}
|
||||
|
||||
## How to think about OpenHuman
|
||||
|
||||
ChatGPT, Claude, Gemini and every other model are the brain. They are brilliant at reasoning. But they are amnesiac. They know nothing about your actual life.
|
||||
|
||||
OpenHuman is the memory and the doer that makes those brains actually useful. It is the context engine that compresses your entire organizational life into intelligence any AI can act on — stored as Markdown you own.
|
||||
OpenHuman is the memory and the doer that makes those brains actually useful. It is the context engine that compresses your entire organizational life into intelligence any AI can act on, stored as Markdown you own.
|
||||
|
||||
Your AI is smart. It just does not know you. OpenHuman fixes that.
|
||||
|
||||
+8
-41
@@ -9,8 +9,8 @@
|
||||
## Features
|
||||
|
||||
* [Obsidian-Style Memory](features/obsidian-wiki/README.md)
|
||||
* [Memory Tree Pipeline](features/obsidian-wiki/memory-tree-pipeline.md)
|
||||
* [Memory Tree](features/obsidian-wiki/memory-tree.md)
|
||||
* [Memory Tree Pipeline](features/obsidian-wiki/memory-tree-pipeline.md)
|
||||
* [Auto-fetch from Integrations](features/auto-fetch.md)
|
||||
* [Third-party Integrations (118+)](features/integrations.md)
|
||||
* [Smart Token Compression](features/token-compression.md)
|
||||
@@ -18,6 +18,10 @@
|
||||
* [Native Voice (ElevenLabs)](features/voice.md)
|
||||
* [Native Tools (search, scraper, coder)](features/native-tools.md)
|
||||
* [Local AI (optional)](features/local-ai.md)
|
||||
* [Subconscious Loop](features/subconscious.md)
|
||||
* [Privacy & Security](features/privacy-and-security.md)
|
||||
* [Platform & Availability](features/platform.md)
|
||||
* [Architecture](features/architecture.md)
|
||||
|
||||
## Settings Reference
|
||||
|
||||
@@ -25,28 +29,6 @@
|
||||
* [Automation & Channels](settings-reference/automation-and-channels.md)
|
||||
* [AI & Skills](settings-reference/ai-and-skills.md)
|
||||
|
||||
## Product
|
||||
|
||||
* [Architecture](product/architecture.md)
|
||||
* [Platform & Availability](product/platform.md)
|
||||
* [Skills & Integrations](product/skills-and-integrations.md)
|
||||
* [Intelligence](product/intelligence.md)
|
||||
* [Messaging Channels](product/messaging-channels.md)
|
||||
* [Privacy & Security](product/privacy-and-security.md)
|
||||
* [Subconscious Loop](product/subconscious.md)
|
||||
* [Cloud Deploy](product/cloud-deploy.md)
|
||||
|
||||
## Use Cases
|
||||
|
||||
* [Overview](use-cases/overview.md)
|
||||
* [Knowledge Workers](use-cases/knowledge-workers.md)
|
||||
* [Individual Power Users](use-cases/individual-power-users.md)
|
||||
* [Traders and Analysts](use-cases/traders-and-analysts.md)
|
||||
* [DAO and Web3 Communities](use-cases/dao-and-web3-communities.md)
|
||||
* [Community Managers & Moderators](use-cases/community-managers-and-moderators.md)
|
||||
* [Distributed Teams](use-cases/distributed-teams.md)
|
||||
* [Service Providers & Contributors](use-cases/service-providers-and-contributors.md)
|
||||
|
||||
## Developing
|
||||
|
||||
* [Overview](developing/README.md)
|
||||
@@ -54,27 +36,12 @@
|
||||
* [Testing Strategy](developing/testing-strategy.md)
|
||||
* [E2E Testing](developing/e2e-testing.md)
|
||||
* [Release Policy](developing/release-policy.md)
|
||||
* [Cloud Deploy](developing/cloud-deploy.md)
|
||||
* [Chromium Embedded Framework](developing/cef.md)
|
||||
* [Coding Harness](developing/coding-harness.md)
|
||||
* [Agent Observability](developing/agent-observability.md)
|
||||
|
||||
## Frontend (app/src/)
|
||||
|
||||
* [Overview](developing/frontend/README.md)
|
||||
* [Architecture](developing/frontend/architecture.md)
|
||||
* [State Management](developing/frontend/state-management.md)
|
||||
* [Services](developing/frontend/services.md)
|
||||
* [Pages & Routing](developing/frontend/pages-routing.md)
|
||||
* [Components](developing/frontend/components.md)
|
||||
* [Providers](developing/frontend/providers.md)
|
||||
* [Hooks & Utilities](developing/frontend/hooks-utils.md)
|
||||
|
||||
## Tauri shell (app/src-tauri/)
|
||||
|
||||
* [Overview](developing/tauri-shell/README.md)
|
||||
* [Architecture](developing/tauri-shell/architecture.md)
|
||||
* [Commands](developing/tauri-shell/commands.md)
|
||||
* [Core Bridge](developing/tauri-shell/core-bridge.md)
|
||||
* [Frontend (app/src/)](developing/frontend.md)
|
||||
* [Tauri Shell (app/src-tauri/)](developing/tauri-shell.md)
|
||||
|
||||
## Legal
|
||||
|
||||
|
||||
@@ -18,35 +18,31 @@ OpenHuman is open source under GNU GPL3 and lives at [github.com/tinyhumansai/op
|
||||
| `docs/` | Remaining deep developer reference (memory pipeline diagrams, telegram-login, sentry, agent flows, etc.). |
|
||||
| `gitbooks/` | Public-facing documentation — this site. |
|
||||
|
||||
The high-level shape lives in [Architecture](../product/architecture.md). The deep developer architecture lives in [Architecture](architecture.md).
|
||||
The high-level shape lives in [Architecture](../features/architecture.md). The deep developer architecture lives in [Architecture](architecture.md).
|
||||
|
||||
## Start here
|
||||
|
||||
* [**Getting Set Up**](getting-set-up.md) — building from source, toolchain, vendored Tauri CLI, sidecar staging.
|
||||
* [**Testing Strategy**](testing-strategy.md) — Vitest, cargo test, WDIO E2E. Where each test goes.
|
||||
* [**E2E Testing**](e2e-testing.md) — running end-to-end specs locally and in CI.
|
||||
* [**Release Policy**](release-policy.md) — release cadence, version policy, OAuth-and-installer rules.
|
||||
* [**Getting Set Up**](getting-set-up.md). building from source, toolchain, vendored Tauri CLI, sidecar staging.
|
||||
* [**Testing Strategy**](testing-strategy.md). Vitest, cargo test, WDIO E2E. Where each test goes.
|
||||
* [**E2E Testing**](e2e-testing.md). running end-to-end specs locally and in CI.
|
||||
* [**Release Policy**](release-policy.md). release cadence, version policy, OAuth-and-installer rules.
|
||||
|
||||
## Building features
|
||||
|
||||
* [**Skills**](/broken/pages/FlgZn0fqfml1B1piRDP7) — how skills are discovered, fetched, installed, initialized, executed and synced.
|
||||
* [**Subconscious Loop**](../product/subconscious.md) — background task evaluation against the workspace.
|
||||
* [**Conscious Loop**](/broken/pages/3E8LUI7rYDOmXWtegL46) — actionable-item extraction from the memory tree.
|
||||
* [**Webview Integration**](/broken/pages/qKYPRr16dx5Vomiw5iaH) — adding a new third-party webview-based integration.
|
||||
* [**Subconscious Loop**](../features/subconscious.md). background task evaluation against the workspace.
|
||||
|
||||
## Working with agents
|
||||
|
||||
* [**Coding Harness**](coding-harness.md) — the agent's code-focused tool surface and how to extend it.
|
||||
* [**Agent Observability**](agent-observability.md) — the artifact-capture layer that makes E2E tests debuggable.
|
||||
* [**Coding Harness**](coding-harness.md). the agent's code-focused tool surface and how to extend it.
|
||||
* [**Agent Observability**](agent-observability.md). the artifact-capture layer that makes E2E tests debuggable.
|
||||
|
||||
## Other contributor docs
|
||||
|
||||
Anything not yet migrated lives under [`docs/`](https://github.com/tinyhumansai/openhuman/tree/main/docs) in the repo. Notable references:
|
||||
|
||||
* [Architecture](architecture.md) — canonical architecture.
|
||||
* [Memory Context Window](/broken/pages/l8k1mXW6gnYt3tp2WIGd) — long-term memory injection presets.
|
||||
* [`docs/PROMPT_INJECTION_GUARD.md`](../../docs/PROMPT_INJECTION_GUARD.md) — security model.
|
||||
* [Frontend chapter docs](frontend/) — React app structure (`app/src/`).
|
||||
* [Tauri shell chapter docs](tauri-shell/) — desktop host (`app/src-tauri/`).
|
||||
* [Architecture](architecture.md). canonical architecture.
|
||||
* [`docs/PROMPT_INJECTION_GUARD.md`](../../docs/PROMPT_INJECTION_GUARD.md). security model.
|
||||
* [Frontend chapter](frontend.md). React app structure (`app/src/`).
|
||||
* [Tauri shell chapter](tauri-shell.md). desktop host (`app/src-tauri/`).
|
||||
|
||||
[`CLAUDE.md`](../../CLAUDE.md) is the source of truth for AI agents working on the codebase, with the same rules contributors are expected to follow.
|
||||
|
||||
@@ -74,7 +74,7 @@ saveMockRequestLog('after-connect-click', getRequestLog());
|
||||
|
||||
`captureCheckpoint` numbers captures so the run dir reads chronologically.
|
||||
`captureFailureArtifacts` is wired into `wdio.conf.ts` and fires
|
||||
automatically on any failing test — specs should not call it directly.
|
||||
automatically on any failing test, specs should not call it directly.
|
||||
|
||||
## What is intentionally out of scope
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ The desktop app **WebView** loads the UI from `app/`; heavy RPC and skills run i
|
||||
|
||||
## Platform reach
|
||||
|
||||
**Supported today (end users):** desktop — Windows, macOS, Linux (native installers).
|
||||
**Supported today (end users):** desktop. Windows, macOS, Linux (native installers).
|
||||
|
||||
**Not supported yet:** Android, iOS, standalone web client (may exist as experimental targets in the repo; do not treat as product-ready).
|
||||
|
||||
@@ -95,9 +95,9 @@ OpenHuman chose Tauri + Rust over Electron for fundamental performance and secur
|
||||
| Memory safety | Compile-time guaranteed | Runtime exceptions |
|
||||
| TLS implementation | rustls (no OpenSSL dependency) | Chromium's BoringSSL |
|
||||
|
||||
**Why this matters for a crypto platform**: Traders and analysts run OpenHuman alongside resource-intensive tools — charting software, multiple browser tabs, trading terminals. A native binary with sub-500ms startup means the app feels native and stays out of the way. Zero GC pauses means real-time price feeds and alerts are never delayed by memory management.
|
||||
**Why this matters for a crypto platform**: Traders and analysts run OpenHuman alongside resource-intensive tools, charting software, multiple browser tabs, trading terminals. A native binary with sub-500ms startup means the app feels native and stays out of the way. Zero GC pauses means real-time price feeds and alerts are never delayed by memory management.
|
||||
|
||||
The **Tokio async runtime** drives all I/O — WebSocket connections, HTTP requests, file operations, and inter-skill communication — as non-blocking tasks on a thread pool. Thousands of concurrent operations (skill executions, cron jobs, socket events) share a small fixed set of OS threads.
|
||||
The **Tokio async runtime** drives all I/O. WebSocket connections, HTTP requests, file operations, and inter-skill communication, as non-blocking tasks on a thread pool. Thousands of concurrent operations (skill executions, cron jobs, socket events) share a small fixed set of OS threads.
|
||||
|
||||
---
|
||||
|
||||
@@ -128,7 +128,7 @@ Desktop Mode: Web Mode:
|
||||
- **Handshake**: WebSocket connect, Engine.IO OPEN (extracts `sid`, `pingInterval`, `pingTimeout`), Socket.IO CONNECT with JWT auth, CONNECT ACK
|
||||
- **Keep-alive**: Responds to Engine.IO PING with PONG; timeout threshold = `pingInterval + pingTimeout + 5s` (default: 50 seconds)
|
||||
- **Reconnection**: Exponential backoff from 1 second to 30 seconds max. Resets to 1s after a successful connection is lost; keeps growing if connection was never established
|
||||
- **CORS bypass**: The Rust `reqwest` HTTP client makes external API calls directly — no browser CORS restrictions apply
|
||||
- **CORS bypass**: The Rust `reqwest` HTTP client makes external API calls directly, no browser CORS restrictions apply
|
||||
|
||||
The socket connection is **shared across all skills**. When events arrive, the socket manager routes them to the appropriate skill via async message channels. This eliminates per-skill connection overhead entirely.
|
||||
|
||||
@@ -163,7 +163,7 @@ OpenHuman's defining capability is its **sandboxed JavaScript execution engine**
|
||||
+---------------------------------------------------------------+
|
||||
```
|
||||
|
||||
**QuickJS Runtime** (`rquickjs`): Each skill gets its own QuickJS `AsyncRuntime` and `AsyncContext` — fully isolated memory spaces with no cross-skill access.
|
||||
**QuickJS Runtime** (`rquickjs`): Each skill gets its own QuickJS `AsyncRuntime` and `AsyncContext`, fully isolated memory spaces with no cross-skill access.
|
||||
|
||||
| Parameter | Value |
|
||||
| ------------------------------ | ----------- |
|
||||
@@ -173,7 +173,7 @@ OpenHuman's defining capability is its **sandboxed JavaScript execution engine**
|
||||
| Graceful stop timeout | 5 seconds |
|
||||
| Message channel buffer | 64 messages |
|
||||
|
||||
**Message-passing architecture**: Skills communicate with the core engine through async MPSC channels — no shared mutable state. The registry routes tool calls, server events, cron triggers, and lifecycle commands to the correct skill instance via its channel sender.
|
||||
**Message-passing architecture**: Skills communicate with the core engine through async MPSC channels, no shared mutable state. The registry routes tool calls, server events, cron triggers, and lifecycle commands to the correct skill instance via its channel sender.
|
||||
|
||||
**Bridge APIs** expose platform capabilities to skill JavaScript code:
|
||||
|
||||
@@ -207,7 +207,7 @@ Skills are synced from a GitHub repository and discovered at runtime. Platform f
|
||||
|
||||
## AI & Tool Protocol (MCP)
|
||||
|
||||
OpenHuman implements the **Model Context Protocol** — a JSON-RPC 2.0 layer over Socket.io that lets AI models discover and invoke tools exposed by skills.
|
||||
OpenHuman implements the **Model Context Protocol**, a JSON-RPC 2.0 layer over Socket.io that lets AI models discover and invoke tools exposed by skills.
|
||||
|
||||
```
|
||||
User Prompt
|
||||
@@ -239,7 +239,7 @@ AI Response to User
|
||||
|
||||
**Transport**: 30-second timeout per request, `mcp:` event prefix, request IDs tracked in a pending response map. Tool names are namespaced as `skillId__toolName` for unambiguous routing.
|
||||
|
||||
**Tool sync**: The `tool:sync` event broadcasts the complete tool inventory — skill ID, name, connection status, and tool list — on every socket connect and skill state change. The backend AI system always has an up-to-date view of available capabilities.
|
||||
**Tool sync**: The `tool:sync` event broadcasts the complete tool inventory, skill ID, name, connection status, and tool list, on every socket connect and skill state change. The backend AI system always has an up-to-date view of available capabilities.
|
||||
|
||||
**AI Memory System**:
|
||||
|
||||
@@ -253,9 +253,6 @@ AI Response to User
|
||||
| Sessions | JSONL transcripts with compaction and tool compression |
|
||||
|
||||
Memory encryption keys derive from user credentials via Argon2id, ensuring memory files are unreadable without authentication. The hybrid search combines semantic understanding (vector similarity) with keyword precision (SQLite FTS5) for reliable recall.
|
||||
|
||||
Skill sync now also feeds a bounded **user working memory** layer (preferences, goals, constraints, recurring entities) via the core runtime sync worker. See [`docs/SKILL-WORKING-MEMORY.md`](./SKILL-WORKING-MEMORY.md) for hook locations, privacy rules, and extension guidance.
|
||||
|
||||
---
|
||||
|
||||
## Security Architecture
|
||||
@@ -278,13 +275,13 @@ Skill sync now also feeds a bounded **user working memory** layer (preferences,
|
||||
+-------------------------------------------------------------------+
|
||||
```
|
||||
|
||||
- **Credential storage**: OS keychain integration via the `keyring` crate (macOS Keychain, Windows Credential Manager, Linux Secret Service) — desktop only
|
||||
- **Credential storage**: OS keychain integration via the `keyring` crate (macOS Keychain, Windows Credential Manager, Linux Secret Service), desktop only
|
||||
- **Memory encryption**: AES-256-GCM with Argon2id key derivation. All AI memory is encrypted at rest
|
||||
- **Skill sandboxing**: Each QuickJS instance has enforced memory limits (64 MB default) and stack limits (512 KB). No cross-skill memory access
|
||||
- **Auth handoff**: Web-to-desktop authentication uses single-use login tokens with 5-minute TTL, exchanged via Rust HTTP client (bypasses CORS)
|
||||
- **Network TLS**: All WebSocket and HTTP connections use rustls — no dependency on platform OpenSSL
|
||||
- **Network TLS**: All WebSocket and HTTP connections use rustls, no dependency on platform OpenSSL
|
||||
- **State management**: Sensitive data lives in Redux (memory) and OS keychain (persistent). No localStorage for credentials or tokens
|
||||
- **Prompt injection guard**: User prompts are normalized/scored and enforced server-side (`allow | review | block`) before model/tool execution. See [`docs/PROMPT_INJECTION_GUARD.md`](./PROMPT_INJECTION_GUARD.md)
|
||||
- **Prompt injection guard**: User prompts are normalized/scored and enforced server-side (`allow | review | block`) before model/tool execution. See [`docs/PROMPT_INJECTION_GUARD.md`](../../docs/PROMPT_INJECTION_GUARD.md)
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ This page explains why CEF is in the bundle, what the codebase uses it for today
|
||||
|
||||
## Why CEF instead of a stock webview
|
||||
|
||||
Stock Tauri uses each platform's native webview — WKWebView on macOS, WebView2 on Windows, WebKitGTK on Linux. Those work fine for rendering the OpenHuman app itself. They have one fatal limitation for our use case: **none of them expose Chrome DevTools Protocol (CDP)**.
|
||||
Stock Tauri uses each platform's native webview. WKWebView on macOS, WebView2 on Windows, WebKitGTK on Linux. Those work fine for rendering the OpenHuman app itself. They have one fatal limitation for our use case: **none of them expose Chrome DevTools Protocol (CDP)**.
|
||||
|
||||
CDP is the load-bearing primitive. Every "watch what's happening inside Slack / WhatsApp / Telegram / Discord / Meet" feature in OpenHuman talks to those embedded apps via CDP, not via injected JavaScript. CDP gives us:
|
||||
|
||||
@@ -62,19 +62,19 @@ Each scan emits `webview:event` payloads and POSTs `openhuman.memory_doc_ingest`
|
||||
|
||||
### Google Meet mascot camera
|
||||
|
||||
The flashiest CEF trick. The Meet agent doesn't just _attend_ a meeting — it **broadcasts** itself as a camera. This works because CEF lets us:
|
||||
The flashiest CEF trick. The Meet agent doesn't just _attend_ a meeting, it **broadcasts** itself as a camera. This works because CEF lets us:
|
||||
|
||||
1. Inject a tiny bridge (`camera_bridge.js`) via `Page.addScriptToEvaluateOnNewDocument` before any Meet code runs.
|
||||
2. Override `navigator.mediaDevices.getUserMedia` so it returns a `MediaStream` from a hidden 640×480 canvas instead of a real camera.
|
||||
3. Render the mascot SVG on that canvas, swapping mood states (idle, thinking, talking) via `window.__openhumanSetMood(...)` driven from Rust over CDP.
|
||||
|
||||
There's also a build-time path that rasterizes the mascot SVG to Y4M and uses CEF's native `--use-file-for-fake-video-capture` flag — a fully native fake-camera source with no JS at all.
|
||||
There's also a build-time path that rasterizes the mascot SVG to Y4M and uses CEF's native `--use-file-for-fake-video-capture` flag, a fully native fake-camera source with no JS at all.
|
||||
|
||||
Code: [`app/src-tauri/src/meet_video/`](https://github.com/tinyhumansai/openhuman/tree/main/app/src-tauri/src/meet_video).
|
||||
|
||||
### Native notification interception
|
||||
|
||||
The fork at `feat/cef-notification-intercept` adds renderer-side shims for `Notification.permission`, `Notification.requestPermission()`, and `navigator.permissions.query({name: "notifications"})`. These now install in the real `tauri-runtime-cef` path on every runtime code path — so when Slack checks if it can show notifications, the answer is consistent with what CEF's permission callbacks already granted.
|
||||
The fork at `feat/cef-notification-intercept` adds renderer-side shims for `Notification.permission`, `Notification.requestPermission()`, and `navigator.permissions.query({name: "notifications"})`. These now install in the real `tauri-runtime-cef` path on every runtime code path, so when Slack checks if it can show notifications, the answer is consistent with what CEF's permission callbacks already granted.
|
||||
|
||||
This is the bulk of `docs/TAURI_CEF_FINDINGS_AND_CHANGES.md`. It's why Slack stops asking the same permission five times in a session.
|
||||
|
||||
@@ -117,7 +117,7 @@ The plumbing is already there. A `@openhuman/browser_task` skill could spin up a
|
||||
|
||||
### Headless CEF for server-side replay
|
||||
|
||||
The same scanner pattern (long-lived WebSocket → IDB walk + DOM snapshot) works without a UI. Headless CEF in the core sidecar could replay sessions on a schedule — useful for users who host the core in the cloud and want auto-fetch from sources that don't expose a clean OAuth API.
|
||||
The same scanner pattern (long-lived WebSocket → IDB walk + DOM snapshot) works without a UI. Headless CEF in the core sidecar could replay sessions on a schedule, useful for users who host the core in the cloud and want auto-fetch from sources that don't expose a clean OAuth API.
|
||||
|
||||
### Privacy hooks at the browser-process layer
|
||||
|
||||
@@ -125,7 +125,7 @@ CEF's `CefRequestHandler` already lets us intercept network requests. A small st
|
||||
|
||||
### CDP-driven testing framework
|
||||
|
||||
The scanner pattern — spawn webview, walk IDB, snapshot DOM, evaluate one ephemeral expression — is structurally identical to E2E test orchestration. We could ship `@openhuman/web_test` as a public skill: `connect_cef → snapshot → evaluate → assert`. Tests written in plain Rust against any web app, no Selenium / Playwright dependency.
|
||||
The scanner pattern, spawn webview, walk IDB, snapshot DOM, evaluate one ephemeral expression, is structurally identical to E2E test orchestration. We could ship `@openhuman/web_test` as a public skill: `connect_cef → snapshot → evaluate → assert`. Tests written in plain Rust against any web app, no Selenium / Playwright dependency.
|
||||
|
||||
### Renderer ↔ Rust message channel
|
||||
|
||||
@@ -133,10 +133,9 @@ Today every CDP `Runtime.evaluate` is fire-and-forget. A long-lived bidirectiona
|
||||
|
||||
### Multi-account merge
|
||||
|
||||
Each connected account gets its own profile and its own IDB. CDP can snapshot one account's IDB, decrypt-merge with another's, and upsert into a shared memory doc — e.g. one unified Slack memory across three workspaces.
|
||||
Each connected account gets its own profile and its own IDB. CDP can snapshot one account's IDB, decrypt-merge with another's, and upsert into a shared memory doc, e.g. one unified Slack memory across three workspaces.
|
||||
|
||||
## See also
|
||||
|
||||
* [Webview Integration](/broken/pages/qKYPRr16dx5Vomiw5iaH) — playbook for adding a new provider.
|
||||
* [`docs/TAURI_CEF_FINDINGS_AND_CHANGES.md`](../../docs/TAURI_CEF_FINDINGS_AND_CHANGES.md) — the notification-permission deep dive.
|
||||
* [`CLAUDE.md`](../../CLAUDE.md) — the canonical "no new JS injection" rule.
|
||||
* [`docs/TAURI_CEF_FINDINGS_AND_CHANGES.md`](../../docs/TAURI_CEF_FINDINGS_AND_CHANGES.md). the notification-permission deep dive.
|
||||
* [`CLAUDE.md`](../../CLAUDE.md). the canonical "no new JS injection" rule.
|
||||
|
||||
@@ -9,7 +9,7 @@ OpenHuman is a desktop app, but its **Rust core** (`openhuman-core`) is a
|
||||
headless JSON-RPC server that can be hosted in the cloud. Deploying the core
|
||||
separately is useful for:
|
||||
|
||||
- Multi-device access — point several desktop clients at the same hosted core
|
||||
- Multi-device access, point several desktop clients at the same hosted core
|
||||
- Internal testers without local Rust toolchains
|
||||
- Long-running cron jobs / webhooks that should outlive a laptop session
|
||||
|
||||
@@ -21,7 +21,7 @@ This guide covers three deploy paths, easiest first:
|
||||
|
||||
What gets deployed in every path: a single container running
|
||||
`openhuman-core serve` on port `7788`, behind the provider's TLS. The desktop
|
||||
app already knows how to talk to a remote core — set
|
||||
app already knows how to talk to a remote core, set
|
||||
`OPENHUMAN_CORE_RPC_URL=https://your-host/rpc` and `OPENHUMAN_CORE_TOKEN=...`
|
||||
in `app/.env.local` and launch.
|
||||
|
||||
@@ -33,16 +33,16 @@ in `app/.env.local` and launch.
|
||||
|----------------------------|----------|-----------------------------------------------------------------------|
|
||||
| `OPENHUMAN_CORE_TOKEN` | yes | Bearer token clients send to `/rpc`. Generate with `openssl rand -hex 32`. **Anyone with this token can drive the core.** |
|
||||
| `BACKEND_URL` | yes | Tinyhumans backend the core talks to (`https://api.tinyhumans.ai` for prod). |
|
||||
| `OPENHUMAN_APP_ENV` | no | `production` or `staging`. Defaults to `staging`. |
|
||||
| `OPENHUMAN_APP_ENV` | no | `production` or `staging`. Defaults to `production`. |
|
||||
| `OPENHUMAN_CORE_HOST` | no | Defaults to `0.0.0.0` in the container. |
|
||||
| `OPENHUMAN_CORE_PORT` | no | Defaults to `7788`. |
|
||||
| `RUST_LOG` | no | `info` is fine; `debug` for triage. |
|
||||
|
||||
Endpoints exposed by the running container:
|
||||
|
||||
- `GET /health` — public liveness probe. Used by every deploy path's healthcheck.
|
||||
- `POST /rpc` — bearer-protected JSON-RPC entrypoint.
|
||||
- `GET /events`, `GET /ws/dictation` — public streaming channels.
|
||||
- `GET /health`, public liveness probe. Used by every deploy path's healthcheck.
|
||||
- `POST /rpc`, bearer-protected JSON-RPC entrypoint.
|
||||
- `GET /events`, `GET /ws/dictation`, public streaming channels.
|
||||
|
||||
The `OPENHUMAN_WORKSPACE` directory (`/home/openhuman/.openhuman` inside the
|
||||
container) holds the core's config, sqlite databases, and skill state. **Mount
|
||||
@@ -54,7 +54,7 @@ restart.
|
||||
## 1. DigitalOcean App Platform: one-click
|
||||
|
||||
Click the button below to create a new App Platform application from this
|
||||
repository's [`.do/app.yaml`](../.do/app.yaml):
|
||||
repository's [`.do/app.yaml`](../../.do/app.yaml):
|
||||
|
||||
[](https://cloud.digitalocean.com/apps/new?repo=https://github.com/tinyhumansai/openhuman/tree/main)
|
||||
|
||||
@@ -65,7 +65,7 @@ Then, in the App Platform UI, **before the first deploy completes**:
|
||||
(`openssl rand -hex 32`). Mark it encrypted.
|
||||
3. If you are deploying staging, change `OPENHUMAN_APP_ENV` to `staging` and
|
||||
`BACKEND_URL` to `https://staging-api.tinyhumans.ai`.
|
||||
4. Hit **Save** — App Platform redeploys with the new secret.
|
||||
4. Hit **Save**. App Platform redeploys with the new secret.
|
||||
|
||||
App Platform handles TLS, restart-on-crash, log streaming, and rolling
|
||||
redeploys on `git push` (set `deploy_on_push: true` in `.do/app.yaml` to
|
||||
@@ -106,7 +106,7 @@ doctl apps update <app-id> --spec .do/app.yaml
|
||||
|
||||
## 3. Any VPS via Docker Compose
|
||||
|
||||
Works on any host with Docker Engine ≥ 24 and the Compose plugin —
|
||||
Works on any host with Docker Engine ≥ 24 and the Compose plugin.
|
||||
DigitalOcean Droplet, Hetzner, Linode, EC2, a home server.
|
||||
|
||||
```bash
|
||||
@@ -129,7 +129,7 @@ docker compose ps
|
||||
curl -fsS http://localhost:7788/health
|
||||
```
|
||||
|
||||
The Compose file ([`docker-compose.yml`](../docker-compose.yml)) maps the core
|
||||
The Compose file ([`docker-compose.yml`](../../docker-compose.yml)) maps the core
|
||||
on `:7788`, mounts a named volume `openhuman-workspace` for persistence, and
|
||||
sets `restart: unless-stopped` so the core comes back after host reboots.
|
||||
|
||||
@@ -178,9 +178,9 @@ calls to the remote core; nothing else changes.
|
||||
|
||||
## Smoke test
|
||||
|
||||
The repo ships [`.github/workflows/deploy-smoke.yml`](../.github/workflows/deploy-smoke.yml),
|
||||
The repo ships [`.github/workflows/deploy-smoke.yml`](../../.github/workflows/deploy-smoke.yml),
|
||||
which runs on every PR that touches the deploy artifacts. It builds the
|
||||
Docker image, boots it, and polls `/health` — so a regression in the cloud
|
||||
Docker image, boots it, and polls `/health`, so a regression in the cloud
|
||||
deploy path fails CI before it lands on `main`.
|
||||
|
||||
To run the same check locally:
|
||||
@@ -10,10 +10,10 @@ agents. This page is the canonical map: which tool the model should
|
||||
reach for, what permissions it needs, and where the implementation
|
||||
lives.
|
||||
|
||||
It is intentionally a flat catalog — not a guide on how the agent
|
||||
It is intentionally a flat catalog, not a guide on how the agent
|
||||
loop dispatches tools. For that, see
|
||||
[`gitbooks/developing/architecture.md`](architecture.md) and
|
||||
[`src/openhuman/tools/`](../src/openhuman/tools/).
|
||||
[`src/openhuman/tools/`](../../src/openhuman/tools/).
|
||||
|
||||
Tracking issue: [#1205](https://github.com/tinyhumansai/openhuman/issues/1205).
|
||||
|
||||
@@ -46,46 +46,46 @@ alias in parentheses is the conceptual role.
|
||||
|
||||
### Navigation
|
||||
|
||||
- **`file_read { path }`** — read a workspace-relative file (≤10 MB).
|
||||
- **`file_read { path }`**. read a workspace-relative file (≤10 MB).
|
||||
Path-sandboxed and symlink-escape blocked.
|
||||
- **`grep { pattern, path?, max_matches?, case_insensitive? }`** —
|
||||
- **`grep { pattern, path?, max_matches?, case_insensitive? }`**.
|
||||
regex search across files. Returns `path:line:text` lines, capped.
|
||||
Skips `.git`, `node_modules`, `target`, `.next`, `dist`, `build`,
|
||||
`.cache`.
|
||||
- **`glob { pattern, max_results? }`** — list files matching a glob
|
||||
- **`glob { pattern, max_results? }`**. list files matching a glob
|
||||
(e.g. `src/**/*.rs`). Sorted newest-first. Same skip set as `grep`.
|
||||
- **`list { path? }`** — non-recursive directory listing. Each line is
|
||||
- **`list { path? }`**. non-recursive directory listing. Each line is
|
||||
`<kind>\t<name>` where kind is `dir`, `file`, or `link`.
|
||||
|
||||
### Editing
|
||||
|
||||
- **`file_write { path, content }`** — overwrite (or create) a file.
|
||||
- **`edit { path, old_string, new_string, replace_all? }`** — exact
|
||||
- **`file_write { path, content }`**. overwrite (or create) a file.
|
||||
- **`edit { path, old_string, new_string, replace_all? }`**. exact
|
||||
string-replace. By default `old_string` must be unique in the file
|
||||
(so the model can't accidentally rewrite every occurrence); set
|
||||
`replace_all` to override.
|
||||
- **`apply_patch { edits[] }`** — atomic batch of `edit` operations
|
||||
- **`apply_patch { edits[] }`**. atomic batch of `edit` operations
|
||||
across one or more files. Validation runs over the whole batch
|
||||
first; if any edit fails (path not allowed, non-unique match, file
|
||||
too large, …) **no** files are written.
|
||||
|
||||
### Execution
|
||||
|
||||
- **`shell { command }`** — run a vetted shell command. Use this only
|
||||
- **`shell { command }`**. run a vetted shell command. Use this only
|
||||
when the right primitive doesn't already exist (e.g. `grep` should
|
||||
almost always replace `shell { command: "grep ..." }`).
|
||||
|
||||
### Interaction & control flow
|
||||
|
||||
- **`ask_clarification` (canonical `question`)** — pause the run and
|
||||
- **`ask_clarification` (canonical `question`)**. pause the run and
|
||||
ask the user a structured question. Resumes with the answer once
|
||||
the user replies.
|
||||
- **`spawn_subagent` (canonical `task`)** — delegate a focused unit of
|
||||
- **`spawn_subagent` (canonical `task`)**. delegate a focused unit of
|
||||
work to a child agent. Returns a single text result.
|
||||
- **`todowrite { todos[] }`** — replace the agent's lightweight todo
|
||||
- **`todowrite { todos[] }`**. replace the agent's lightweight todo
|
||||
list. Each item is `{content, status}` where `status ∈
|
||||
{pending, in_progress, completed}`. Only one `in_progress` allowed.
|
||||
- **`plan_exit { plan }`** — emit a `[plan_exit]` marker plus the
|
||||
- **`plan_exit { plan }`**. emit a `[plan_exit]` marker plus the
|
||||
plan text, signaling that the plan-mode pass is done and the
|
||||
harness should hand off to a build-mode pass. The plan→build
|
||||
switch on the harness side is follow-up work; the marker is stable
|
||||
@@ -93,9 +93,9 @@ alias in parentheses is the conceptual role.
|
||||
|
||||
### Web research
|
||||
|
||||
- **`web_search` (canonical `websearch`)** — backend-proxied search
|
||||
- **`web_search` (canonical `websearch`)**. backend-proxied search
|
||||
via Parallel. Returns ranked excerpts.
|
||||
- **`web_fetch` (canonical `webfetch`)** — single-purpose `GET` →
|
||||
- **`web_fetch` (canonical `webfetch`)**. single-purpose `GET` →
|
||||
text body, capped. Reuses the same `allowed_domains` gate as
|
||||
`http_request`. Reach for `web_fetch` when reading docs/READMEs;
|
||||
reach for `http_request` only when you need methods, headers, or
|
||||
@@ -103,18 +103,18 @@ alias in parentheses is the conceptual role.
|
||||
|
||||
### Code intelligence
|
||||
|
||||
- **`lsp { kind, language, file, line?, character?, symbol? }`** —
|
||||
- **`lsp { kind, language, file, line?, character?, symbol? }`**.
|
||||
capability-gated. Registered only when `OPENHUMAN_LSP_ENABLED=1`.
|
||||
Schema is stable; the language-server backend is a follow-up — the
|
||||
Schema is stable; the language-server backend is a follow-up, the
|
||||
current implementation returns a clear `not yet implemented` error
|
||||
when called, so callers can feature-detect.
|
||||
|
||||
## Permissions and modes
|
||||
|
||||
Permissions live on the [`Tool`
|
||||
trait](../src/openhuman/tools/traits.rs). Each tool returns one of:
|
||||
trait](../../src/openhuman/tools/traits.rs). Each tool returns one of:
|
||||
`None`, `ReadOnly`, `Write`, `Execute`, `Dangerous`. Channels can set
|
||||
a maximum permission level — anything above is rejected before
|
||||
a maximum permission level, anything above is rejected before
|
||||
execution.
|
||||
|
||||
Today's coding-harness mapping:
|
||||
@@ -172,6 +172,6 @@ baseline PR:
|
||||
- Richer permission model (per-tool channel allowlists, per-call
|
||||
approval policies).
|
||||
- Controller-registry exposure of the new agent-only tools to
|
||||
JSON-RPC. Today they remain agent-only — the registry already
|
||||
JSON-RPC. Today they remain agent-only, the registry already
|
||||
exposes `tools_web_search` (and friends) where the Tauri shell
|
||||
needs them.
|
||||
|
||||
@@ -79,9 +79,9 @@ Requires Docker Desktop or Colima. The repo is bind-mounted so builds persist be
|
||||
|
||||
`app/test/e2e/helpers/platform.ts` exports:
|
||||
|
||||
- `isTauriDriver()` — `true` on Linux (tauri-driver session)
|
||||
- `isMac2()` — `true` on macOS (Appium Mac2 session)
|
||||
- `supportsExecuteScript()` — `true` when `browser.execute()` works (tauri-driver only)
|
||||
- `isTauriDriver()`, `true` on Linux (tauri-driver session)
|
||||
- `isMac2()`, `true` on macOS (Appium Mac2 session)
|
||||
- `supportsExecuteScript()`, `true` when `browser.execute()` works (tauri-driver only)
|
||||
|
||||
### Element helpers
|
||||
|
||||
@@ -108,7 +108,7 @@ Requires Docker Desktop or Colima. The repo is bind-mounted so builds persist be
|
||||
|
||||
### Writing cross-platform specs
|
||||
|
||||
1. **Use helpers** from `element-helpers.ts` — never use raw `XCUIElementType*` selectors in specs
|
||||
1. **Use helpers** from `element-helpers.ts`, never use raw `XCUIElementType*` selectors in specs
|
||||
2. **Use `clickNativeButton(text)`** instead of inline button-clicking code
|
||||
3. **Use `hasAppChrome()`** instead of checking for `XCUIElementTypeMenuBar`
|
||||
4. **Use `waitForWebView()`** instead of checking for `XCUIElementTypeWebView`
|
||||
@@ -185,10 +185,10 @@ The first Docker build compiles Rust + tauri-driver from source. Subsequent runs
|
||||
|
||||
Tests notification RPC methods via the live core sidecar and the Notifications UI page:
|
||||
|
||||
- `notification_ingest` — creates a new notification via core RPC
|
||||
- `notification_list` — verifies the ingested notification is returned
|
||||
- `notification_mark_read` — marks a notification as read
|
||||
- `notification_stats` — checks aggregate statistics shape
|
||||
- `notification_ingest`, creates a new notification via core RPC
|
||||
- `notification_list`, verifies the ingested notification is returned
|
||||
- `notification_mark_read`, marks a notification as read
|
||||
- `notification_stats`, checks aggregate statistics shape
|
||||
- UI: Notifications page renders the integration notifications section (`[data-testid="integration-notifications-section"]`)
|
||||
- UI: Notifications page shows the System Events section (`[data-testid="system-events-section"]`)
|
||||
|
||||
@@ -198,7 +198,7 @@ Tests notification RPC methods via the live core sidecar and the Notifications U
|
||||
bash app/scripts/e2e-run-spec.sh test/e2e/specs/notifications.spec.ts notifications
|
||||
```
|
||||
|
||||
**Platform note**: RPC tests (`notification_ingest`, `notification_list`, `notification_mark_read`, `notification_stats`) run on both Linux (tauri-driver) and macOS (Appium Mac2). UI assertions (Notifications page sections) require Linux / tauri-driver because `browser.execute()` is unavailable on Mac2 — those tests auto-skip when `supportsExecuteScript()` returns `false`.
|
||||
**Platform note**: RPC tests (`notification_ingest`, `notification_list`, `notification_mark_read`, `notification_stats`) run on both Linux (tauri-driver) and macOS (Appium Mac2). UI assertions (Notifications page sections) require Linux / tauri-driver because `browser.execute()` is unavailable on Mac2, those tests auto-skip when `supportsExecuteScript()` returns `false`.
|
||||
|
||||
---
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,63 +0,0 @@
|
||||
---
|
||||
description: Index for the React + Vite frontend (app/src/) — architecture, state, services, components, hooks.
|
||||
icon: browsers
|
||||
---
|
||||
|
||||
# Source code documentation (`app/src/`)
|
||||
|
||||
This section describes the **OpenHuman** desktop app frontend: the Vite + React tree under **`app/src/`** in the monorepo (Yarn workspace `openhuman-app`).
|
||||
|
||||
## Quick reference
|
||||
|
||||
| Document | Description |
|
||||
| --------------------------------------------- | --------------------------------------------------- |
|
||||
| [Architecture overview](./01-architecture.md) | Entry points, provider chain, module relationships |
|
||||
| [State management](./02-state-management.md) | Redux Toolkit, persistence, selectors |
|
||||
| [Services layer](./03-services.md) | API client, Socket.io, core RPC client |
|
||||
| [MCP utilities](./04-mcp-system.md) | Transport and types (no bundled Telegram tool pack) |
|
||||
| [Pages & routing](./05-pages-routing.md) | `HashRouter`, guards, main routes |
|
||||
| [Components](./06-components.md) | UI and settings patterns |
|
||||
| [Providers](./07-providers.md) | User, Socket, AI, Skill providers |
|
||||
| [Hooks & utils](./08-hooks-utils.md) | Shared hooks and helpers |
|
||||
|
||||
## Scale (approximate)
|
||||
|
||||
| Metric | Value |
|
||||
| --------------------------------------- | --------------------------------------------------------------------------- |
|
||||
| TypeScript / TSX files under `app/src/` | ~285 (run `find app/src -name '*.ts' -o -name '*.tsx' \| wc -l` to refresh) |
|
||||
| Test runner | Vitest (`app/test/vitest.config.ts`) |
|
||||
|
||||
## Directory layout (high level)
|
||||
|
||||
```
|
||||
app/src/
|
||||
├── App.tsx # Provider chain + HashRouter shell
|
||||
├── AppRoutes.tsx # Route table + guards
|
||||
├── main.tsx # Entry (Sentry, store, styles)
|
||||
├── store/ # Redux slices and selectors
|
||||
├── providers/ # UserProvider, SocketProvider, AIProvider, SkillProvider
|
||||
├── services/ # apiClient, socketService, coreRpcClient, api/*
|
||||
├── lib/ # AI loaders, MCP helpers, skills sync, etc.
|
||||
├── pages/ # Route-level screens
|
||||
├── components/ # Shared UI
|
||||
├── hooks/ # App hooks
|
||||
├── utils/ # Config, Tauri helpers, routing utilities
|
||||
└── assets/ # Icons and static assets
|
||||
```
|
||||
|
||||
## Architectural decisions
|
||||
|
||||
1. **HashRouter** — Fits Tauri and deep-link flows better than browser history in many desktop setups.
|
||||
2. **Redux Toolkit + persist** — Centralized state; selective persistence for auth and related slices.
|
||||
3. **Core RPC client** — Business logic and skills run in the **`openhuman`** Rust sidecar; the UI calls it via HTTP (`core_rpc_relay` / `coreRpcClient`), not only REST.
|
||||
4. **No MTProto provider** — The current tree does not ship a `TelegramProvider` or `mtprotoService`; any Telegram mentions may be legacy UI or future channels work.
|
||||
|
||||
## Getting started
|
||||
|
||||
1. Read [Architecture overview](./01-architecture.md).
|
||||
2. Skim [State management](./02-state-management.md) and [Services](./03-services.md).
|
||||
3. Use [Pages & routing](./05-pages-routing.md) when changing navigation.
|
||||
|
||||
---
|
||||
|
||||
_Documentation for the `app/src/` tree; paths are relative to the repository root._
|
||||
@@ -1,99 +0,0 @@
|
||||
---
|
||||
description: System architecture of the React frontend — provider chain, build, layout, conventions.
|
||||
icon: code-branch
|
||||
---
|
||||
|
||||
# Architecture overview
|
||||
|
||||
## System architecture
|
||||
|
||||
OpenHuman’s desktop UI is a **React 19** app (`app/src/`) that:
|
||||
|
||||
- Uses **Redux Toolkit** with persistence for session-related state
|
||||
- Connects to the backend with **REST** (`apiClient`) and **Socket.io** (`socketService`)
|
||||
- Calls the **Rust core** process over HTTP via **`coreRpcClient`** / Tauri **`core_rpc_relay`** (JSON-RPC methods implemented in repo root `src/openhuman/`, exposed through `core_server`)
|
||||
- Loads **AI prompts** from bundled `src/openhuman/agent/prompts` (repo root) and from Tauri **`ai_get_config`** when packaged
|
||||
- Uses a **minimal MCP-style** helper layer under `lib/mcp/` (transport, validation) — not a large in-repo Telegram MCP tool bundle
|
||||
|
||||
## Entry points
|
||||
|
||||
| File | Purpose |
|
||||
| ----------------------- | ------------------------------------------------------------------------------------ |
|
||||
| `app/src/main.tsx` | React root, Sentry boundary, store, global styles |
|
||||
| `app/src/App.tsx` | Provider chain: Redux → PersistGate → User → Socket → AI → Skill → Router |
|
||||
| `app/src/AppRoutes.tsx` | `HashRouter` routes, `ProtectedRoute` / `PublicRoute`, onboarding and mnemonic gates |
|
||||
|
||||
## Provider chain
|
||||
|
||||
```
|
||||
Redux Provider
|
||||
└─ PersistGate
|
||||
└─ UserProvider
|
||||
└─ SocketProvider
|
||||
└─ AIProvider
|
||||
└─ SkillProvider
|
||||
└─ HashRouter
|
||||
└─ AppRoutes (pages + settings)
|
||||
```
|
||||
|
||||
**Why this order**
|
||||
|
||||
1. Redux is outermost for `useAppSelector` / dispatch everywhere.
|
||||
2. `PersistGate` rehydrates persisted slices before children assume stable auth.
|
||||
3. `SocketProvider` uses the auth token for Socket.io.
|
||||
4. `AIProvider` / `SkillProvider` wrap features that depend on socket and store state.
|
||||
5. `HashRouter` supplies navigation to all routes.
|
||||
|
||||
## Module relationships (simplified)
|
||||
|
||||
```
|
||||
App.tsx
|
||||
├─ Redux store + persistor
|
||||
├─ UserProvider — user profile / workspace context
|
||||
├─ SocketProvider — connects socketService when token present
|
||||
├─ AIProvider — AI session / memory client coordination
|
||||
├─ SkillProvider — skills catalog and sync
|
||||
└─ AppRoutes
|
||||
├─ PublicRoute — e.g. Welcome on `/`
|
||||
├─ ProtectedRoute — onboarding, home, skills, settings, …
|
||||
└─ DefaultRedirect — unauthenticated users
|
||||
```
|
||||
|
||||
## Services layer (conceptual)
|
||||
|
||||
```
|
||||
services/
|
||||
├─ apiClient → REST to a URL resolved at runtime via `services/backendUrl#getBackendUrl`
|
||||
├─ backendUrl → Calls `openhuman.config_resolve_api_url`; falls back to VITE_BACKEND_URL only outside Tauri
|
||||
├─ socketService → Socket.io; realtime + MCP-style envelopes
|
||||
└─ coreRpcClient → HTTP to local openhuman core (JSON-RPC), used with Tauri relay
|
||||
```
|
||||
|
||||
### Runtime config precedence
|
||||
|
||||
The desktop app does not bake the core RPC URL or the API host into the
|
||||
bundle as a hard requirement. At runtime the app resolves them in this order
|
||||
(highest first):
|
||||
|
||||
1. **Login-screen RPC URL field** — saved via `utils/configPersistence` and
|
||||
restored on next launch. End users configure the sidecar address here, not
|
||||
by hand-editing `config.toml` or `.env` files.
|
||||
2. **Tauri `core_rpc_url` command** — the port the bundled sidecar is
|
||||
listening on for this process.
|
||||
3. **`VITE_OPENHUMAN_CORE_RPC_URL`** — build-time fallback for development.
|
||||
4. The hardcoded `http://127.0.0.1:7788/rpc` default.
|
||||
|
||||
Once the RPC handshake succeeds, `services/backendUrl` calls
|
||||
`openhuman.config_resolve_api_url` to pull `api_url` (and other safe client
|
||||
fields) from the loaded core `Config`. `VITE_BACKEND_URL` is only used as a
|
||||
web fallback when the app runs outside Tauri.
|
||||
|
||||
Components that need the backend URL should call `useBackendUrl()` (or
|
||||
`getBackendUrl()` from non-React code) — they must not import the static
|
||||
`BACKEND_URL` constant from `utils/config`, which represents the build-time
|
||||
value only.
|
||||
|
||||
## Related docs
|
||||
|
||||
- Rust architecture: [`../ARCHITECTURE.md`](../ARCHITECTURE.md)
|
||||
- Tauri shell: [`../src-tauri/01-architecture.md`](../src-tauri/01-architecture.md)
|
||||
@@ -1,518 +0,0 @@
|
||||
---
|
||||
description: Reusable React components organized by feature.
|
||||
icon: puzzle-piece
|
||||
---
|
||||
|
||||
# Components
|
||||
|
||||
Reusable React components organized by feature.
|
||||
|
||||
## Component Structure
|
||||
|
||||
```
|
||||
components/
|
||||
├── Route Guards
|
||||
│ ├── ProtectedRoute.tsx
|
||||
│ ├── PublicRoute.tsx
|
||||
│ └── DefaultRedirect.tsx
|
||||
│
|
||||
├── Authentication
|
||||
│ └── TelegramLoginButton.tsx
|
||||
│
|
||||
├── Connection Status
|
||||
│ ├── ConnectionIndicator.tsx
|
||||
│ ├── TelegramConnectionIndicator.tsx
|
||||
│ ├── TelegramConnectionModal.tsx
|
||||
│ └── GmailConnectionIndicator.tsx
|
||||
│
|
||||
├── Onboarding
|
||||
│ ├── ProgressIndicator.tsx
|
||||
│ └── LottieAnimation.tsx
|
||||
│
|
||||
├── Settings Modal (16 files)
|
||||
│ ├── SettingsModal.tsx
|
||||
│ ├── SettingsLayout.tsx
|
||||
│ ├── SettingsHome.tsx
|
||||
│ ├── panels/
|
||||
│ ├── components/
|
||||
│ └── hooks/
|
||||
│
|
||||
└── Development
|
||||
└── DesignSystemShowcase.tsx
|
||||
```
|
||||
|
||||
## Route Guard Components
|
||||
|
||||
### ProtectedRoute
|
||||
|
||||
Requires authentication and optionally onboarding.
|
||||
|
||||
```typescript
|
||||
interface ProtectedRouteProps {
|
||||
requireOnboarded?: boolean;
|
||||
}
|
||||
|
||||
// Usage in AppRoutes.tsx
|
||||
<Route element={<ProtectedRoute />}>
|
||||
<Route path="/onboarding/*" element={<Onboarding />} />
|
||||
</Route>
|
||||
|
||||
<Route element={<ProtectedRoute requireOnboarded />}>
|
||||
<Route path="/home" element={<Home />} />
|
||||
</Route>
|
||||
```
|
||||
|
||||
### PublicRoute
|
||||
|
||||
Redirects authenticated users away.
|
||||
|
||||
```typescript
|
||||
// Usage in AppRoutes.tsx
|
||||
<Route element={<PublicRoute />}>
|
||||
<Route path="/" element={<Welcome />} />
|
||||
<Route path="/login" element={<Login />} />
|
||||
</Route>
|
||||
```
|
||||
|
||||
### DefaultRedirect
|
||||
|
||||
Fallback that routes based on auth state.
|
||||
|
||||
```typescript
|
||||
// Redirects to:
|
||||
// - "/" if not authenticated
|
||||
// - "/onboarding" if authenticated but not onboarded
|
||||
// - "/home" if authenticated and onboarded
|
||||
```
|
||||
|
||||
## Authentication Components
|
||||
|
||||
### TelegramLoginButton
|
||||
|
||||
OAuth login button for Telegram.
|
||||
|
||||
```typescript
|
||||
interface TelegramLoginButtonProps {
|
||||
onClick: () => void;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
// Usage
|
||||
<TelegramLoginButton
|
||||
onClick={() => openUrl(`${BACKEND_URL}/auth/telegram?platform=desktop`)}
|
||||
/>
|
||||
```
|
||||
|
||||
## Connection Status Components
|
||||
|
||||
### ConnectionIndicator
|
||||
|
||||
Generic connection status badge.
|
||||
|
||||
```typescript
|
||||
interface ConnectionIndicatorProps {
|
||||
status: 'connected' | 'connecting' | 'disconnected' | 'error';
|
||||
label?: string;
|
||||
}
|
||||
|
||||
<ConnectionIndicator status="connected" label="Socket" />
|
||||
```
|
||||
|
||||
### TelegramConnectionIndicator
|
||||
|
||||
Telegram-specific status display.
|
||||
|
||||
```typescript
|
||||
interface TelegramConnectionIndicatorProps {
|
||||
status: 'connected' | 'connecting' | 'disconnected' | 'error';
|
||||
}
|
||||
|
||||
// Usage with Redux state
|
||||
const telegramStatus = useAppSelector((state) =>
|
||||
selectTelegramConnectionStatus(state, userId)
|
||||
);
|
||||
|
||||
<TelegramConnectionIndicator status={telegramStatus} />
|
||||
```
|
||||
|
||||
### TelegramConnectionModal
|
||||
|
||||
Modal for setting up Telegram connection.
|
||||
|
||||
```typescript
|
||||
interface TelegramConnectionModalProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
// Usage in onboarding/settings
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
|
||||
<TelegramConnectionModal
|
||||
isOpen={showModal}
|
||||
onClose={() => setShowModal(false)}
|
||||
/>
|
||||
```
|
||||
|
||||
**Features:**
|
||||
|
||||
- QR code login flow
|
||||
- Phone number login flow
|
||||
- Connection status display
|
||||
- Error handling
|
||||
|
||||
### GmailConnectionIndicator
|
||||
|
||||
Gmail status badge (future integration).
|
||||
|
||||
```typescript
|
||||
<GmailConnectionIndicator status="coming-soon" />
|
||||
```
|
||||
|
||||
## Onboarding Components
|
||||
|
||||
### ProgressIndicator
|
||||
|
||||
Visual progress through onboarding steps.
|
||||
|
||||
```typescript
|
||||
interface ProgressIndicatorProps {
|
||||
current: number;
|
||||
total: number;
|
||||
}
|
||||
|
||||
<ProgressIndicator current={2} total={5} />
|
||||
```
|
||||
|
||||
### LottieAnimation
|
||||
|
||||
Lottie animation player for onboarding.
|
||||
|
||||
```typescript
|
||||
interface LottieAnimationProps {
|
||||
animationData: object;
|
||||
loop?: boolean;
|
||||
autoplay?: boolean;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
import welcomeAnimation from '../assets/animations/welcome.json';
|
||||
|
||||
<LottieAnimation
|
||||
animationData={welcomeAnimation}
|
||||
loop={true}
|
||||
autoplay={true}
|
||||
/>
|
||||
```
|
||||
|
||||
## Settings Modal System
|
||||
|
||||
Complete modal system with URL-based routing.
|
||||
|
||||
### File Structure
|
||||
|
||||
```
|
||||
components/settings/
|
||||
├── SettingsModal.tsx # Route-based container
|
||||
├── SettingsLayout.tsx # Portal + backdrop wrapper
|
||||
├── SettingsHome.tsx # Main menu with profile
|
||||
├── panels/
|
||||
│ ├── ConnectionsPanel.tsx # Connection management
|
||||
│ ├── MessagingPanel.tsx # (Future)
|
||||
│ ├── PrivacyPanel.tsx # (Future)
|
||||
│ ├── ProfilePanel.tsx # (Future)
|
||||
│ ├── AdvancedPanel.tsx # (Future)
|
||||
│ └── BillingPanel.tsx # (Future)
|
||||
├── components/
|
||||
│ ├── SettingsHeader.tsx # User profile section
|
||||
│ ├── SettingsMenuItem.tsx # Menu item component
|
||||
│ ├── SettingsBackButton.tsx # Back navigation
|
||||
│ └── SettingsPanelLayout.tsx# Panel wrapper
|
||||
└── hooks/
|
||||
├── useSettingsNavigation.ts # URL routing
|
||||
└── useSettingsAnimation.ts # Animation state
|
||||
```
|
||||
|
||||
### SettingsModal
|
||||
|
||||
Main container that renders based on URL.
|
||||
|
||||
```typescript
|
||||
export function SettingsModal() {
|
||||
const location = useLocation();
|
||||
const isOpen = location.pathname.startsWith('/settings');
|
||||
|
||||
if (!isOpen) return null;
|
||||
|
||||
return (
|
||||
<SettingsLayout>
|
||||
{/* Route to appropriate panel */}
|
||||
{location.pathname === '/settings' && <SettingsHome />}
|
||||
{location.pathname === '/settings/connections' && <ConnectionsPanel />}
|
||||
{/* ... more panels */}
|
||||
</SettingsLayout>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### SettingsLayout
|
||||
|
||||
Portal-based modal wrapper.
|
||||
|
||||
```typescript
|
||||
export function SettingsLayout({ children }) {
|
||||
const { closeModal } = useSettingsNavigation();
|
||||
|
||||
return createPortal(
|
||||
<div className="fixed inset-0 z-50">
|
||||
{/* Backdrop */}
|
||||
<div
|
||||
className="absolute inset-0 bg-black/50 backdrop-blur-sm"
|
||||
onClick={closeModal}
|
||||
/>
|
||||
|
||||
{/* Modal */}
|
||||
<div className="absolute inset-4 flex items-center justify-center">
|
||||
<div className="bg-white rounded-2xl w-full max-w-[520px] shadow-xl">
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</div>,
|
||||
document.body
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### SettingsHome
|
||||
|
||||
Main menu with user profile.
|
||||
|
||||
```typescript
|
||||
export function SettingsHome() {
|
||||
const { navigateTo, closeModal } = useSettingsNavigation();
|
||||
const user = useAppSelector((state) => state.user.profile);
|
||||
|
||||
const menuItems = [
|
||||
{ id: 'connections', label: 'Connections', icon: LinkIcon },
|
||||
{ id: 'messaging', label: 'Messaging', icon: MessageIcon },
|
||||
{ id: 'privacy', label: 'Privacy', icon: ShieldIcon },
|
||||
// ... more items
|
||||
];
|
||||
|
||||
return (
|
||||
<div>
|
||||
<SettingsHeader user={user} onClose={closeModal} />
|
||||
|
||||
{menuItems.map((item) => (
|
||||
<SettingsMenuItem
|
||||
key={item.id}
|
||||
{...item}
|
||||
onClick={() => navigateTo(item.id)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### ConnectionsPanel
|
||||
|
||||
Connection management interface.
|
||||
|
||||
```typescript
|
||||
export function ConnectionsPanel() {
|
||||
const { navigateBack } = useSettingsNavigation();
|
||||
const [telegramModalOpen, setTelegramModalOpen] = useState(false);
|
||||
|
||||
const telegramStatus = useAppSelector((state) =>
|
||||
selectTelegramConnectionStatus(state, userId)
|
||||
);
|
||||
|
||||
// Reuses connectOptions from onboarding
|
||||
const connections = connectOptions.map((opt) => ({
|
||||
...opt,
|
||||
status: opt.id === 'telegram' ? telegramStatus : 'coming-soon'
|
||||
}));
|
||||
|
||||
return (
|
||||
<SettingsPanelLayout title="Connections" onBack={navigateBack}>
|
||||
{connections.map((conn) => (
|
||||
<ConnectionItem
|
||||
key={conn.id}
|
||||
{...conn}
|
||||
onConnect={() => conn.id === 'telegram' && setTelegramModalOpen(true)}
|
||||
/>
|
||||
))}
|
||||
|
||||
<TelegramConnectionModal
|
||||
isOpen={telegramModalOpen}
|
||||
onClose={() => setTelegramModalOpen(false)}
|
||||
/>
|
||||
</SettingsPanelLayout>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Settings Hooks
|
||||
|
||||
#### useSettingsNavigation
|
||||
|
||||
URL-based navigation for settings modal.
|
||||
|
||||
```typescript
|
||||
interface UseSettingsNavigationReturn {
|
||||
currentRoute: string;
|
||||
navigateTo: (panel: string) => void;
|
||||
navigateBack: () => void;
|
||||
closeModal: () => void;
|
||||
}
|
||||
|
||||
const { navigateTo, navigateBack, closeModal } = useSettingsNavigation();
|
||||
|
||||
// Navigate to panel
|
||||
navigateTo('connections'); // → /settings/connections
|
||||
|
||||
// Go back
|
||||
navigateBack(); // → /settings
|
||||
|
||||
// Close modal
|
||||
closeModal(); // → previous non-settings route
|
||||
```
|
||||
|
||||
#### useSettingsAnimation
|
||||
|
||||
Animation state management.
|
||||
|
||||
```typescript
|
||||
interface UseSettingsAnimationReturn {
|
||||
isEntering: boolean;
|
||||
isExiting: boolean;
|
||||
animationClass: string;
|
||||
}
|
||||
|
||||
const { animationClass } = useSettingsAnimation();
|
||||
|
||||
<div className={`modal ${animationClass}`}>
|
||||
{/* Content */}
|
||||
</div>
|
||||
```
|
||||
|
||||
### Settings Components
|
||||
|
||||
#### SettingsHeader
|
||||
|
||||
User profile section at top of settings.
|
||||
|
||||
```typescript
|
||||
interface SettingsHeaderProps {
|
||||
user: User | null;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
<SettingsHeader user={user} onClose={handleClose} />
|
||||
```
|
||||
|
||||
#### SettingsMenuItem
|
||||
|
||||
Individual menu item with icon and chevron.
|
||||
|
||||
```typescript
|
||||
interface SettingsMenuItemProps {
|
||||
label: string;
|
||||
icon: React.ComponentType;
|
||||
onClick: () => void;
|
||||
badge?: string;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
<SettingsMenuItem
|
||||
label="Connections"
|
||||
icon={LinkIcon}
|
||||
onClick={() => navigateTo('connections')}
|
||||
badge="2"
|
||||
/>
|
||||
```
|
||||
|
||||
#### SettingsBackButton
|
||||
|
||||
Back navigation button.
|
||||
|
||||
```typescript
|
||||
interface SettingsBackButtonProps {
|
||||
onClick: () => void;
|
||||
}
|
||||
|
||||
<SettingsBackButton onClick={navigateBack} />
|
||||
```
|
||||
|
||||
#### SettingsPanelLayout
|
||||
|
||||
Wrapper for settings panels.
|
||||
|
||||
```typescript
|
||||
interface SettingsPanelLayoutProps {
|
||||
title: string;
|
||||
onBack: () => void;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
<SettingsPanelLayout title="Connections" onBack={navigateBack}>
|
||||
{/* Panel content */}
|
||||
</SettingsPanelLayout>
|
||||
```
|
||||
|
||||
## Component Patterns
|
||||
|
||||
### Reusing Connection Options
|
||||
|
||||
The `connectOptions` array is shared between onboarding and settings:
|
||||
|
||||
```typescript
|
||||
// Defined in ConnectStep.tsx, imported elsewhere
|
||||
export const connectOptions = [
|
||||
{
|
||||
id: 'telegram',
|
||||
label: 'Telegram',
|
||||
icon: TelegramIcon,
|
||||
description: 'Connect your Telegram account',
|
||||
},
|
||||
{
|
||||
id: 'gmail',
|
||||
label: 'Gmail',
|
||||
icon: GmailIcon,
|
||||
description: 'Connect your Gmail account',
|
||||
comingSoon: true,
|
||||
},
|
||||
];
|
||||
```
|
||||
|
||||
### Modal via Portal
|
||||
|
||||
Settings modal uses `createPortal` to render outside the component tree:
|
||||
|
||||
```typescript
|
||||
return createPortal(
|
||||
<div className="modal-container">
|
||||
{/* Modal content */}
|
||||
</div>,
|
||||
document.body
|
||||
);
|
||||
```
|
||||
|
||||
### Controlled vs Uncontrolled
|
||||
|
||||
Connection modals are controlled components:
|
||||
|
||||
```typescript
|
||||
// Parent controls open state
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
<TelegramConnectionModal
|
||||
isOpen={isOpen}
|
||||
onClose={() => setIsOpen(false)}
|
||||
/>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
_Previous: [Pages & Routing](./05-pages-routing.md) | Next: [Providers](./07-providers.md)_
|
||||
@@ -1,506 +0,0 @@
|
||||
---
|
||||
description: Shared hooks and utility functions in the frontend.
|
||||
icon: anchor
|
||||
---
|
||||
|
||||
# Hooks & Utilities
|
||||
|
||||
Custom React hooks and utility functions.
|
||||
|
||||
## Custom Hooks
|
||||
|
||||
### useSocket (`hooks/useSocket.ts`)
|
||||
|
||||
Access Socket.io functionality from any component.
|
||||
|
||||
```typescript
|
||||
interface UseSocketReturn {
|
||||
socket: Socket | null;
|
||||
isConnected: boolean;
|
||||
emit: (event: string, data: unknown) => void;
|
||||
on: (event: string, handler: Function) => void;
|
||||
off: (event: string, handler: Function) => void;
|
||||
once: (event: string, handler: Function) => void;
|
||||
}
|
||||
|
||||
function useSocket(): UseSocketReturn;
|
||||
```
|
||||
|
||||
**Usage:**
|
||||
|
||||
```typescript
|
||||
import { useSocket } from "../hooks/useSocket";
|
||||
|
||||
function ChatInput() {
|
||||
const { emit, isConnected } = useSocket();
|
||||
|
||||
const sendMessage = (text: string) => {
|
||||
if (isConnected) {
|
||||
emit("chat:message", { text });
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<input
|
||||
disabled={!isConnected}
|
||||
onKeyDown={(e) => e.key === "Enter" && sendMessage(e.target.value)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
**With event listeners:**
|
||||
|
||||
```typescript
|
||||
function Notifications() {
|
||||
const { on, off } = useSocket();
|
||||
const [notifications, setNotifications] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
const handler = (notification) => {
|
||||
setNotifications((prev) => [...prev, notification]);
|
||||
};
|
||||
|
||||
on("notification", handler);
|
||||
return () => off("notification", handler);
|
||||
}, [on, off]);
|
||||
|
||||
return <NotificationList items={notifications} />;
|
||||
}
|
||||
```
|
||||
|
||||
### useUser (`hooks/useUser.ts`)
|
||||
|
||||
Access user profile data and loading state.
|
||||
|
||||
```typescript
|
||||
interface UseUserReturn {
|
||||
user: User | null;
|
||||
loading: boolean;
|
||||
error: string | null;
|
||||
refetch: () => Promise<void>;
|
||||
}
|
||||
|
||||
function useUser(): UseUserReturn;
|
||||
```
|
||||
|
||||
**Usage:**
|
||||
|
||||
```typescript
|
||||
import { useUser } from "../hooks/useUser";
|
||||
|
||||
function ProfileHeader() {
|
||||
const { user, loading, error, refetch } = useUser();
|
||||
|
||||
if (loading) return <Skeleton />;
|
||||
if (error) return <Error message={error} onRetry={refetch} />;
|
||||
if (!user) return null;
|
||||
|
||||
return (
|
||||
<div className="profile">
|
||||
<Avatar src={user.avatar} />
|
||||
<span>
|
||||
{user.firstName} {user.lastName}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Settings Modal Hooks
|
||||
|
||||
#### useSettingsNavigation (`components/settings/hooks/useSettingsNavigation.ts`)
|
||||
|
||||
URL-based navigation for settings modal.
|
||||
|
||||
```typescript
|
||||
interface UseSettingsNavigationReturn {
|
||||
currentRoute: string; // Current settings path
|
||||
navigateTo: (panel: string) => void; // Navigate to panel
|
||||
navigateBack: () => void; // Go back one level
|
||||
closeModal: () => void; // Close settings entirely
|
||||
}
|
||||
|
||||
function useSettingsNavigation(): UseSettingsNavigationReturn;
|
||||
```
|
||||
|
||||
**Usage:**
|
||||
|
||||
```typescript
|
||||
import { useSettingsNavigation } from "./hooks/useSettingsNavigation";
|
||||
|
||||
function SettingsMenu() {
|
||||
const { navigateTo, closeModal } = useSettingsNavigation();
|
||||
|
||||
return (
|
||||
<nav>
|
||||
<button onClick={() => navigateTo("connections")}>Connections</button>
|
||||
<button onClick={() => navigateTo("privacy")}>Privacy</button>
|
||||
<button onClick={closeModal}>Close</button>
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
#### useSettingsAnimation (`components/settings/hooks/useSettingsAnimation.ts`)
|
||||
|
||||
Animation state management for settings modal.
|
||||
|
||||
```typescript
|
||||
interface UseSettingsAnimationReturn {
|
||||
isEntering: boolean; // Modal is animating in
|
||||
isExiting: boolean; // Modal is animating out
|
||||
animationClass: string; // CSS class for current state
|
||||
}
|
||||
|
||||
function useSettingsAnimation(): UseSettingsAnimationReturn;
|
||||
```
|
||||
|
||||
**Usage:**
|
||||
|
||||
```typescript
|
||||
import { useSettingsAnimation } from "./hooks/useSettingsAnimation";
|
||||
|
||||
function SettingsModal() {
|
||||
const { animationClass, isExiting } = useSettingsAnimation();
|
||||
|
||||
return <div className={`modal ${animationClass}`}>{/* Content */}</div>;
|
||||
}
|
||||
```
|
||||
|
||||
## Utilities
|
||||
|
||||
### Configuration (`utils/config.ts`)
|
||||
|
||||
Build-time environment variable access. These constants only carry the value
|
||||
that was baked into the bundle — for the **runtime** URL the app actually
|
||||
talks to, see `services/backendUrl` and `hooks/useBackendUrl` below.
|
||||
|
||||
```typescript
|
||||
// Build-time fallback only (used outside Tauri).
|
||||
export const BACKEND_URL = import.meta.env.VITE_BACKEND_URL || 'https://api.example.com';
|
||||
|
||||
// Debug mode
|
||||
export const DEBUG = import.meta.env.VITE_DEBUG === 'true';
|
||||
```
|
||||
|
||||
**Usage (build-time only — feature flags, debug toggles, …):**
|
||||
|
||||
```typescript
|
||||
import { DEBUG } from '../utils/config';
|
||||
|
||||
if (DEBUG) {
|
||||
console.log('debug enabled');
|
||||
}
|
||||
```
|
||||
|
||||
> **Do not** import `BACKEND_URL` directly to make API calls. Resolve the URL
|
||||
> at runtime so the core sidecar's `api_url` (set on the login screen via
|
||||
> `openhuman.config_resolve_api_url`) takes effect:
|
||||
>
|
||||
> ```typescript
|
||||
> // React components
|
||||
> import { useBackendUrl } from '../hooks/useBackendUrl';
|
||||
> const backendUrl = useBackendUrl();
|
||||
>
|
||||
> // Non-React code
|
||||
> import { getBackendUrl } from '../services/backendUrl';
|
||||
> const backendUrl = await getBackendUrl();
|
||||
> ```
|
||||
|
||||
### Deep Link (`utils/deeplink.ts`)
|
||||
|
||||
Build deep link URLs for authentication handoff.
|
||||
|
||||
```typescript
|
||||
// Build auth deep link
|
||||
function buildAuthDeepLink(token: string): string;
|
||||
|
||||
// Parse deep link URL
|
||||
function parseDeepLink(url: string): { path: string; params: URLSearchParams };
|
||||
```
|
||||
|
||||
**Usage:**
|
||||
|
||||
```typescript
|
||||
import { buildAuthDeepLink } from '../utils/deeplink';
|
||||
|
||||
// Build URL for browser redirect
|
||||
const deepLink = buildAuthDeepLink(loginToken);
|
||||
// → "openhuman://auth?token=abc123"
|
||||
|
||||
// In web frontend after auth:
|
||||
window.location.href = deepLink;
|
||||
```
|
||||
|
||||
### Desktop Deep Link Listener (`utils/desktopDeepLinkListener.ts`)
|
||||
|
||||
Handle incoming deep links in desktop app.
|
||||
|
||||
```typescript
|
||||
// Setup listener for deep link events
|
||||
async function setupDesktopDeepLinkListener(): Promise<void>;
|
||||
```
|
||||
|
||||
**Called in main.tsx:**
|
||||
|
||||
```typescript
|
||||
// Lazy import to ensure Tauri IPC is ready
|
||||
import('./utils/desktopDeepLinkListener').then(m => {
|
||||
m.setupDesktopDeepLinkListener().catch(console.error);
|
||||
});
|
||||
```
|
||||
|
||||
**What it does:**
|
||||
|
||||
1. Listens for `onOpenUrl` events from Tauri deep-link plugin
|
||||
2. Parses `openhuman://auth?token=...` URLs
|
||||
3. Calls Rust `exchange_token` command (bypasses CORS)
|
||||
4. Stores session in Redux
|
||||
5. Navigates to `/onboarding` or `/home`
|
||||
|
||||
**Loop prevention:**
|
||||
|
||||
```typescript
|
||||
// Set flag before navigation to prevent reprocessing
|
||||
localStorage.setItem('deepLinkHandled', 'true');
|
||||
window.location.replace('/');
|
||||
|
||||
// On next load, clear flag
|
||||
if (localStorage.getItem('deepLinkHandled') === 'true') {
|
||||
localStorage.removeItem('deepLinkHandled');
|
||||
return; // Don't process again
|
||||
}
|
||||
```
|
||||
|
||||
### URL Opener (`utils/openUrl.ts`)
|
||||
|
||||
Cross-platform URL opening.
|
||||
|
||||
```typescript
|
||||
// Open URL in system browser
|
||||
async function openUrl(url: string): Promise<void>;
|
||||
```
|
||||
|
||||
**Usage:**
|
||||
|
||||
```typescript
|
||||
import { openUrl } from '../utils/openUrl';
|
||||
|
||||
// Opens in system browser (not in-app WebView)
|
||||
await openUrl('https://telegram.org/auth');
|
||||
```
|
||||
|
||||
**Implementation:**
|
||||
|
||||
```typescript
|
||||
export async function openUrl(url: string): Promise<void> {
|
||||
try {
|
||||
// Try Tauri opener plugin first
|
||||
const { open } = await import('@tauri-apps/plugin-opener');
|
||||
await open(url);
|
||||
} catch {
|
||||
// Fallback to browser API
|
||||
window.open(url, '_blank');
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Polyfills (`polyfills.ts`)
|
||||
|
||||
Node.js polyfills for browser environment.
|
||||
|
||||
The `telegram` npm package requires Node.js APIs. These are polyfilled:
|
||||
|
||||
```typescript
|
||||
// polyfills.ts
|
||||
import { Buffer } from 'buffer';
|
||||
import process from 'process';
|
||||
import util from 'util';
|
||||
|
||||
window.Buffer = Buffer;
|
||||
window.process = process;
|
||||
window.util = util;
|
||||
```
|
||||
|
||||
**Imported at app entry:**
|
||||
|
||||
```typescript
|
||||
// main.tsx
|
||||
import './polyfills';
|
||||
|
||||
// ... rest of app
|
||||
```
|
||||
|
||||
**Vite configuration:**
|
||||
|
||||
```typescript
|
||||
// vite.config.ts
|
||||
export default defineConfig({
|
||||
resolve: { alias: { buffer: 'buffer', process: 'process/browser', util: 'util' } },
|
||||
define: { 'process.env': {}, global: 'globalThis' },
|
||||
});
|
||||
```
|
||||
|
||||
## Types
|
||||
|
||||
### API Types (`types/api.ts`)
|
||||
|
||||
```typescript
|
||||
// API response wrapper
|
||||
interface ApiResponse<T> {
|
||||
success: boolean;
|
||||
data?: T;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
// API error
|
||||
interface ApiError {
|
||||
code: string;
|
||||
message: string;
|
||||
details?: unknown;
|
||||
}
|
||||
|
||||
// User interface
|
||||
interface User {
|
||||
id: string;
|
||||
firstName: string;
|
||||
lastName?: string;
|
||||
username?: string;
|
||||
email?: string;
|
||||
avatar?: string;
|
||||
telegramId?: string;
|
||||
subscription?: SubscriptionInfo;
|
||||
usage?: UsageInfo;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
```
|
||||
|
||||
### Onboarding Types (`types/onboarding.ts`)
|
||||
|
||||
```typescript
|
||||
// Onboarding step definition
|
||||
interface OnboardingStep {
|
||||
id: string;
|
||||
title: string;
|
||||
component: React.ComponentType<StepProps>;
|
||||
}
|
||||
|
||||
// Step component props
|
||||
interface StepProps {
|
||||
onNext: () => void;
|
||||
onBack: () => void;
|
||||
}
|
||||
|
||||
// Connection option
|
||||
interface ConnectionOption {
|
||||
id: string;
|
||||
label: string;
|
||||
icon: React.ComponentType;
|
||||
description: string;
|
||||
comingSoon?: boolean;
|
||||
}
|
||||
```
|
||||
|
||||
## Static Data
|
||||
|
||||
### Countries (`data/countries.ts`)
|
||||
|
||||
Country list for phone number input.
|
||||
|
||||
```typescript
|
||||
interface Country {
|
||||
code: string; // "US"
|
||||
name: string; // "United States"
|
||||
dialCode: string; // "+1"
|
||||
flag: string; // "🇺🇸"
|
||||
}
|
||||
|
||||
export const countries: Country[];
|
||||
```
|
||||
|
||||
**Usage:**
|
||||
|
||||
```typescript
|
||||
import { countries } from "../data/countries";
|
||||
|
||||
function PhoneInput() {
|
||||
const [country, setCountry] = useState(countries[0]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<select
|
||||
value={country.code}
|
||||
onChange={(e) =>
|
||||
setCountry(countries.find((c) => c.code === e.target.value))
|
||||
}
|
||||
>
|
||||
{countries.map((c) => (
|
||||
<option key={c.code} value={c.code}>
|
||||
{c.flag} {c.name} ({c.dialCode})
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<input placeholder="Phone number" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Hook Dependencies
|
||||
|
||||
Always include dependencies in useEffect:
|
||||
|
||||
```typescript
|
||||
// Good
|
||||
useEffect(() => {
|
||||
on('event', handler);
|
||||
return () => off('event', handler);
|
||||
}, [on, off, handler]);
|
||||
|
||||
// Bad - missing dependencies
|
||||
useEffect(() => {
|
||||
on('event', handler);
|
||||
return () => off('event', handler);
|
||||
}, []);
|
||||
```
|
||||
|
||||
### Cleanup Functions
|
||||
|
||||
Always clean up subscriptions:
|
||||
|
||||
```typescript
|
||||
useEffect(() => {
|
||||
const subscription = subscribe();
|
||||
return () => subscription.unsubscribe();
|
||||
}, []);
|
||||
```
|
||||
|
||||
### Error Boundaries
|
||||
|
||||
Wrap utility calls in try-catch:
|
||||
|
||||
```typescript
|
||||
try {
|
||||
await openUrl(url);
|
||||
} catch (error) {
|
||||
console.error('Failed to open URL:', error);
|
||||
// Fallback behavior
|
||||
}
|
||||
```
|
||||
|
||||
### Type Safety
|
||||
|
||||
Use TypeScript generics for API calls:
|
||||
|
||||
```typescript
|
||||
const user = await apiClient.get<User>('/users/me');
|
||||
// user is typed as User
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
_Previous: [Providers](./07-providers.md) | [Back to Index](./README.md)_
|
||||
@@ -1,428 +0,0 @@
|
||||
---
|
||||
description: HashRouter routes, route guards, screen organization.
|
||||
icon: compass
|
||||
---
|
||||
|
||||
# Pages & Routing
|
||||
|
||||
The application uses HashRouter with protected and public route guards.
|
||||
|
||||
## Route structure
|
||||
|
||||
Defined in **`app/src/AppRoutes.tsx`** (HashRouter). Approximate map:
|
||||
|
||||
```
|
||||
/ → Welcome (public wrapper)
|
||||
/onboarding → Onboarding (auth, onboarding not complete)
|
||||
/mnemonic → Mnemonic / encryption setup (auth)
|
||||
/home → Home (auth + onboarding + encryption key)
|
||||
/intelligence → Intelligence (auth)
|
||||
/skills → Skills (auth)
|
||||
/conversations → Conversations (auth)
|
||||
/invites → Invites (auth)
|
||||
/agents → Agents (auth)
|
||||
/settings/* → Settings (auth)
|
||||
* → DefaultRedirect
|
||||
```
|
||||
|
||||
There is **no** top-level `/login` route in `AppRoutes`; authentication flows are handled via welcome/onboarding and backend redirects.
|
||||
|
||||
## Route Configuration (`AppRoutes.tsx`)
|
||||
|
||||
```typescript
|
||||
export function AppRoutes() {
|
||||
return (
|
||||
<>
|
||||
<Routes>
|
||||
{/* Public routes - redirect if authenticated */}
|
||||
<Route element={<PublicRoute />}>
|
||||
<Route path="/" element={<Welcome />} />
|
||||
<Route path="/login" element={<Login />} />
|
||||
</Route>
|
||||
|
||||
{/* Protected routes - require authentication */}
|
||||
<Route element={<ProtectedRoute />}>
|
||||
<Route path="/onboarding/*" element={<Onboarding />} />
|
||||
</Route>
|
||||
|
||||
{/* Protected + onboarded routes */}
|
||||
<Route element={<ProtectedRoute requireOnboarded />}>
|
||||
<Route path="/home" element={<Home />} />
|
||||
</Route>
|
||||
|
||||
{/* Fallback redirect */}
|
||||
<Route path="*" element={<DefaultRedirect />} />
|
||||
</Routes>
|
||||
|
||||
{/* Settings modal overlay - renders on top of routes */}
|
||||
<SettingsModal />
|
||||
</>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Route Guards
|
||||
|
||||
### PublicRoute (`components/PublicRoute.tsx`)
|
||||
|
||||
Redirects authenticated users away from public pages.
|
||||
|
||||
```typescript
|
||||
export function PublicRoute() {
|
||||
const token = useAppSelector((state) => state.auth.token);
|
||||
const isOnboarded = useAppSelector((state) =>
|
||||
selectIsOnboarded(state, userId),
|
||||
);
|
||||
|
||||
if (token) {
|
||||
// Authenticated - redirect to appropriate page
|
||||
return <Navigate to={isOnboarded ? "/home" : "/onboarding"} replace />;
|
||||
}
|
||||
|
||||
return <Outlet />;
|
||||
}
|
||||
```
|
||||
|
||||
### ProtectedRoute (`components/ProtectedRoute.tsx`)
|
||||
|
||||
Enforces authentication and optionally onboarding status.
|
||||
|
||||
```typescript
|
||||
interface ProtectedRouteProps {
|
||||
requireOnboarded?: boolean;
|
||||
}
|
||||
|
||||
export function ProtectedRoute({ requireOnboarded = false }) {
|
||||
const token = useAppSelector((state) => state.auth.token);
|
||||
const isOnboarded = useAppSelector((state) =>
|
||||
selectIsOnboarded(state, userId),
|
||||
);
|
||||
|
||||
if (!token) {
|
||||
return <Navigate to="/login" replace />;
|
||||
}
|
||||
|
||||
if (requireOnboarded && !isOnboarded) {
|
||||
return <Navigate to="/onboarding" replace />;
|
||||
}
|
||||
|
||||
return <Outlet />;
|
||||
}
|
||||
```
|
||||
|
||||
### DefaultRedirect (`components/DefaultRedirect.tsx`)
|
||||
|
||||
Fallback route that redirects based on auth state.
|
||||
|
||||
```typescript
|
||||
export function DefaultRedirect() {
|
||||
const token = useAppSelector((state) => state.auth.token);
|
||||
const isOnboarded = useAppSelector((state) =>
|
||||
selectIsOnboarded(state, userId),
|
||||
);
|
||||
|
||||
if (!token) {
|
||||
return <Navigate to="/" replace />;
|
||||
}
|
||||
|
||||
if (!isOnboarded) {
|
||||
return <Navigate to="/onboarding" replace />;
|
||||
}
|
||||
|
||||
return <Navigate to="/home" replace />;
|
||||
}
|
||||
```
|
||||
|
||||
## Pages
|
||||
|
||||
### Welcome Page (`pages/Welcome.tsx`)
|
||||
|
||||
Landing page for unauthenticated users.
|
||||
|
||||
**Features:**
|
||||
|
||||
- App introduction and branding
|
||||
- CTA to login/signup
|
||||
- Public route (redirects if authenticated)
|
||||
|
||||
### Login Page (`pages/Login.tsx`)
|
||||
|
||||
Authentication page.
|
||||
|
||||
**Features:**
|
||||
|
||||
- Telegram OAuth button
|
||||
- Opens `/auth/telegram?platform=desktop` in browser
|
||||
- Handles deep link callback
|
||||
|
||||
```typescript
|
||||
export function Login() {
|
||||
const handleTelegramLogin = () => {
|
||||
// Opens Telegram OAuth in system browser
|
||||
openUrl(`${BACKEND_URL}/auth/telegram?platform=desktop`);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="login-page">
|
||||
<TelegramLoginButton onClick={handleTelegramLogin} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Home Page (`pages/Home.tsx`)
|
||||
|
||||
Main dashboard after authentication.
|
||||
|
||||
**Features:**
|
||||
|
||||
- Protected route (requires auth + onboarded)
|
||||
- Connection status indicators
|
||||
- Navigation to settings modal
|
||||
- Future: Chat list, messages, etc.
|
||||
|
||||
```typescript
|
||||
export function Home() {
|
||||
const navigate = useNavigate();
|
||||
const user = useAppSelector((state) => state.user.profile);
|
||||
const telegramStatus = useAppSelector((state) =>
|
||||
selectTelegramConnectionStatus(state, user?.id),
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="home-page">
|
||||
<header>
|
||||
<h1>Welcome, {user?.firstName}</h1>
|
||||
<button onClick={() => navigate("/settings")}>Settings</button>
|
||||
</header>
|
||||
|
||||
<TelegramConnectionIndicator status={telegramStatus} />
|
||||
<ConnectionIndicator />
|
||||
|
||||
{/* Main content */}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Onboarding Flow (`pages/onboarding/`)
|
||||
|
||||
Multi-step onboarding process.
|
||||
|
||||
### Structure
|
||||
|
||||
```
|
||||
pages/onboarding/
|
||||
├── Onboarding.tsx # Flow controller
|
||||
└── steps/
|
||||
├── GetStartedStep.tsx # Welcome
|
||||
├── PrivacyStep.tsx # Privacy policy
|
||||
├── AnalyticsStep.tsx # Analytics opt-in
|
||||
├── ConnectStep.tsx # Telegram connection
|
||||
└── FeaturesStep.tsx # Features overview
|
||||
```
|
||||
|
||||
### Onboarding Controller (`Onboarding.tsx`)
|
||||
|
||||
```typescript
|
||||
const STEPS = [
|
||||
{ id: "get-started", component: GetStartedStep },
|
||||
{ id: "privacy", component: PrivacyStep },
|
||||
{ id: "analytics", component: AnalyticsStep },
|
||||
{ id: "connect", component: ConnectStep },
|
||||
{ id: "features", component: FeaturesStep },
|
||||
];
|
||||
|
||||
export function Onboarding() {
|
||||
const [currentStep, setCurrentStep] = useState(0);
|
||||
const dispatch = useAppDispatch();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const handleNext = () => {
|
||||
if (currentStep < STEPS.length - 1) {
|
||||
setCurrentStep(currentStep + 1);
|
||||
} else {
|
||||
// Complete onboarding
|
||||
dispatch(setOnboarded({ userId, isOnboarded: true }));
|
||||
navigate("/home");
|
||||
}
|
||||
};
|
||||
|
||||
const handleBack = () => {
|
||||
if (currentStep > 0) {
|
||||
setCurrentStep(currentStep - 1);
|
||||
}
|
||||
};
|
||||
|
||||
const StepComponent = STEPS[currentStep].component;
|
||||
|
||||
return (
|
||||
<div className="onboarding">
|
||||
<ProgressIndicator current={currentStep} total={STEPS.length} />
|
||||
<StepComponent onNext={handleNext} onBack={handleBack} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Step Components
|
||||
|
||||
Each step receives `onNext` and `onBack` callbacks:
|
||||
|
||||
```typescript
|
||||
interface StepProps {
|
||||
onNext: () => void;
|
||||
onBack: () => void;
|
||||
}
|
||||
|
||||
export function ConnectStep({ onNext, onBack }: StepProps) {
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
const telegramStatus = useAppSelector(/* ... */);
|
||||
|
||||
return (
|
||||
<div className="step">
|
||||
<h2>Connect Your Accounts</h2>
|
||||
|
||||
{connectOptions.map((option) => (
|
||||
<ConnectionOption
|
||||
key={option.id}
|
||||
{...option}
|
||||
onClick={() => option.id === "telegram" && setShowModal(true)}
|
||||
/>
|
||||
))}
|
||||
|
||||
<TelegramConnectionModal
|
||||
isOpen={showModal}
|
||||
onClose={() => setShowModal(false)}
|
||||
/>
|
||||
|
||||
<div className="actions">
|
||||
<button onClick={onBack}>Back</button>
|
||||
<button onClick={onNext}>Continue</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Settings Modal Routing
|
||||
|
||||
The settings modal overlays existing content using URL-based routing.
|
||||
|
||||
### Modal Detection
|
||||
|
||||
```typescript
|
||||
// In SettingsModal.tsx
|
||||
const location = useLocation();
|
||||
const isOpen = location.pathname.startsWith("/settings");
|
||||
```
|
||||
|
||||
### Sub-Routes
|
||||
|
||||
```
|
||||
/settings → SettingsHome (main menu)
|
||||
/settings/connections → ConnectionsPanel
|
||||
/settings/messaging → MessagingPanel (future)
|
||||
/settings/privacy → PrivacyPanel (future)
|
||||
/settings/profile → ProfilePanel (future)
|
||||
/settings/advanced → AdvancedPanel (future)
|
||||
/settings/billing → BillingPanel (future)
|
||||
```
|
||||
|
||||
### Navigation
|
||||
|
||||
```typescript
|
||||
import { useSettingsNavigation } from "./hooks/useSettingsNavigation";
|
||||
|
||||
function SettingsHome() {
|
||||
const { navigateTo, closeModal } = useSettingsNavigation();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<SettingsMenuItem
|
||||
label="Connections"
|
||||
onClick={() => navigateTo("connections")}
|
||||
/>
|
||||
<button onClick={closeModal}>Close</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## HashRouter vs BrowserRouter
|
||||
|
||||
The app uses HashRouter for desktop compatibility:
|
||||
|
||||
```typescript
|
||||
// App.tsx
|
||||
import { HashRouter } from "react-router-dom";
|
||||
|
||||
// URLs look like: app://localhost/#/home
|
||||
// Instead of: app://localhost/home
|
||||
```
|
||||
|
||||
**Why HashRouter:**
|
||||
|
||||
1. Tauri deep links work with hash-based URLs
|
||||
2. No server configuration needed
|
||||
3. Works with file:// protocol
|
||||
4. Prevents 404 on direct URL access
|
||||
|
||||
## Deep Link Handling
|
||||
|
||||
Deep links are handled before routing:
|
||||
|
||||
```typescript
|
||||
// main.tsx
|
||||
import("./utils/desktopDeepLinkListener").then((m) => {
|
||||
m.setupDesktopDeepLinkListener().catch(console.error);
|
||||
});
|
||||
```
|
||||
|
||||
The listener intercepts `openhuman://auth?token=...` and:
|
||||
|
||||
1. Exchanges token via Rust command
|
||||
2. Stores session in Redux
|
||||
3. Navigates to `/onboarding` or `/home`
|
||||
|
||||
## Navigation Patterns
|
||||
|
||||
### Programmatic Navigation
|
||||
|
||||
```typescript
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
// Navigate to route
|
||||
navigate("/home");
|
||||
|
||||
// Replace history entry
|
||||
navigate("/login", { replace: true });
|
||||
|
||||
// Go back
|
||||
navigate(-1);
|
||||
```
|
||||
|
||||
### Link Component
|
||||
|
||||
```typescript
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
<Link to="/settings">Settings</Link>;
|
||||
```
|
||||
|
||||
### State Transfer
|
||||
|
||||
```typescript
|
||||
// Pass state to route
|
||||
navigate("/details", { state: { itemId: 123 } });
|
||||
|
||||
// Receive state
|
||||
const location = useLocation();
|
||||
const { itemId } = location.state;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
_Previous: [MCP System](./04-mcp-system.md) | Next: [Components](./06-components.md)_
|
||||
@@ -1,242 +0,0 @@
|
||||
---
|
||||
description: React context providers for service lifecycle and shared state.
|
||||
icon: layer-group
|
||||
---
|
||||
|
||||
# Providers
|
||||
|
||||
React context providers manage service lifecycle and provide shared state.
|
||||
|
||||
## Provider chain
|
||||
|
||||
The providers wrap the application in a specific order (`app/src/App.tsx`):
|
||||
|
||||
```tsx
|
||||
<Sentry.ErrorBoundary>
|
||||
<Provider store={store}>
|
||||
<PersistGate persistor={persistor} onBeforeLift={...}>
|
||||
<UserProvider>
|
||||
<SocketProvider>
|
||||
<AIProvider>
|
||||
<SkillProvider>
|
||||
<Router>
|
||||
<AppRoutes />
|
||||
</Router>
|
||||
</SkillProvider>
|
||||
</AIProvider>
|
||||
</SocketProvider>
|
||||
</UserProvider>
|
||||
</PersistGate>
|
||||
</Provider>
|
||||
</Sentry.ErrorBoundary>
|
||||
```
|
||||
|
||||
(`Router` is `HashRouter` from `react-router-dom`.)
|
||||
|
||||
**Order matters because:**
|
||||
|
||||
1. Redux is outermost for store access.
|
||||
2. `PersistGate` rehydrates persisted slices before children rely on auth.
|
||||
3. `SocketProvider` uses the JWT from the store.
|
||||
4. `AIProvider` / `SkillProvider` depend on socket and store-backed features.
|
||||
5. The router supplies navigation to all routes.
|
||||
|
||||
## SocketProvider (`app/src/providers/SocketProvider.tsx`)
|
||||
|
||||
Manages realtime connectivity: **web** uses the JS Socket.io client; **Tauri** bridges to the Rust socket via `utils/tauriSocket.ts` and reports status back to Redux.
|
||||
|
||||
### Responsibilities
|
||||
|
||||
- Connect when `auth.token` is available; disconnect when cleared
|
||||
- In Tauri: install listeners once, connect Rust socket, coordinate daemon lifecycle (`useDaemonLifecycle`)
|
||||
- Update Redux socket slice / connection status
|
||||
|
||||
### Implementation
|
||||
|
||||
See **`app/src/providers/SocketProvider.tsx`**. The file branches on **`isTauri()`**: web mode uses `socketService` directly; Tauri sets up `tauriSocket` listeners and `connectRustSocket` / `disconnectRustSocket`. Do not treat the pseudocode below as the live implementation.
|
||||
|
||||
### Usage
|
||||
|
||||
```typescript
|
||||
import { useSocket } from '../providers/SocketProvider';
|
||||
|
||||
function MyComponent() {
|
||||
const { socket, isConnected, emit, on, off } = useSocket();
|
||||
|
||||
useEffect(() => {
|
||||
const handler = (data) => console.log('Received:', data);
|
||||
on('event-name', handler);
|
||||
return () => off('event-name', handler);
|
||||
}, [on, off]);
|
||||
|
||||
const sendMessage = () => {
|
||||
emit('send-message', { text: 'Hello!' });
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<span>Status: {isConnected ? 'Connected' : 'Disconnected'}</span>
|
||||
<button onClick={sendMessage}>Send</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## AIProvider (`app/src/providers/AIProvider.tsx`)
|
||||
|
||||
Initializes **memory**, **sessions**, **tool registry** (including memory + web-search tools), **entity manager**, **LLM / embedding providers**, and **constitution** loading. Exposes `useAI()` for children. Heavy logic lives under `app/src/lib/ai/`.
|
||||
|
||||
## SkillProvider (`app/src/providers/SkillProvider.tsx`)
|
||||
|
||||
On mount (when authenticated), discovers skills from the **QuickJS** skills engine via Tauri helpers (`runtimeDiscoverSkills`), syncs manifests into Redux, listens for skill-related Tauri events, and can auto-start configured skills in development.
|
||||
|
||||
## UserProvider (`providers/UserProvider.tsx`)
|
||||
|
||||
Minimal user context provider (most user state is in Redux).
|
||||
|
||||
### Responsibilities
|
||||
|
||||
- Legacy user context for compatibility
|
||||
- May be deprecated in favor of Redux
|
||||
|
||||
### Implementation
|
||||
|
||||
```typescript
|
||||
interface UserContextValue {
|
||||
user: User | null;
|
||||
loading: boolean;
|
||||
}
|
||||
|
||||
export function UserProvider({ children }) {
|
||||
const user = useAppSelector((state) => state.user.profile);
|
||||
const loading = useAppSelector((state) => state.user.loading);
|
||||
|
||||
return (
|
||||
<UserContext.Provider value={{ user, loading }}>
|
||||
{children}
|
||||
</UserContext.Provider>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Usage
|
||||
|
||||
```typescript
|
||||
import { useUserContext } from '../providers/UserProvider';
|
||||
|
||||
function Header() {
|
||||
const { user, loading } = useUserContext();
|
||||
|
||||
if (loading) return <Skeleton />;
|
||||
if (!user) return null;
|
||||
|
||||
return <span>Welcome, {user.firstName}</span>;
|
||||
}
|
||||
```
|
||||
|
||||
## Provider Patterns
|
||||
|
||||
### Effect-Based Lifecycle
|
||||
|
||||
Providers use `useEffect` to manage service lifecycle:
|
||||
|
||||
```typescript
|
||||
useEffect(() => {
|
||||
// Setup on mount or dependency change
|
||||
service.connect();
|
||||
|
||||
// Cleanup on unmount or dependency change
|
||||
return () => {
|
||||
service.disconnect();
|
||||
};
|
||||
}, [dependencies]);
|
||||
```
|
||||
|
||||
### Redux Integration
|
||||
|
||||
Providers read from and dispatch to Redux:
|
||||
|
||||
```typescript
|
||||
// Read state
|
||||
const token = useAppSelector((state) => state.auth.token);
|
||||
|
||||
// Dispatch actions
|
||||
const dispatch = useAppDispatch();
|
||||
dispatch(setStatus({ userId, status: "connected" }));
|
||||
```
|
||||
|
||||
### Parallel initialization
|
||||
|
||||
`SkillProvider` and `AIProvider` may kick off several async tasks on mount (skill discovery, memory init, constitution load). Prefer reading the source for ordering guarantees rather than assuming parallel `Promise.all` everywhere.
|
||||
|
||||
### Session Restoration
|
||||
|
||||
Providers restore persisted state on mount:
|
||||
|
||||
```typescript
|
||||
useEffect(() => {
|
||||
if (persistedSession) {
|
||||
service.restoreSession(persistedSession);
|
||||
}
|
||||
}, [persistedSession]);
|
||||
```
|
||||
|
||||
## Context vs Redux
|
||||
|
||||
| Use Context For | Use Redux For |
|
||||
| ---------------------------------- | ---------------------------------- |
|
||||
| Service instances (socket, client) | Serializable state (status, data) |
|
||||
| Methods (emit, on, off) | Persisted state (sessions, tokens) |
|
||||
| Derived values | Complex state logic |
|
||||
|
||||
Example:
|
||||
|
||||
- `SocketContext` provides `socket` instance and `emit` method
|
||||
- Redux stores `socketStatus` and `socketId`
|
||||
|
||||
## Testing Providers
|
||||
|
||||
### Mock Provider for Tests
|
||||
|
||||
```typescript
|
||||
// test-utils.tsx
|
||||
const mockSocketContext: SocketContextValue = {
|
||||
socket: null,
|
||||
isConnected: true,
|
||||
emit: jest.fn(),
|
||||
on: jest.fn(),
|
||||
off: jest.fn()
|
||||
};
|
||||
|
||||
export function TestProviders({ children }) {
|
||||
return (
|
||||
<Provider store={testStore}>
|
||||
<SocketContext.Provider value={mockSocketContext}>
|
||||
{children}
|
||||
</SocketContext.Provider>
|
||||
</Provider>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Testing Provider Effects
|
||||
|
||||
```typescript
|
||||
test('SocketProvider connects when token is available', () => {
|
||||
const store = createTestStore({ auth: { token: 'test-token' } });
|
||||
|
||||
render(
|
||||
<Provider store={store}>
|
||||
<SocketProvider>
|
||||
<TestComponent />
|
||||
</SocketProvider>
|
||||
</Provider>
|
||||
);
|
||||
|
||||
expect(socketService.connect).toHaveBeenCalledWith('test-token');
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
_Previous: [Components](./06-components.md) | Next: [Hooks & Utils](./08-hooks-utils.md)_
|
||||
@@ -1,226 +0,0 @@
|
||||
---
|
||||
description: Singleton service layer — apiClient, socketService, coreRpcClient and friends.
|
||||
icon: server
|
||||
---
|
||||
|
||||
# Services Layer
|
||||
|
||||
The application uses singleton services for external communication. This prevents connection leaks and provides consistent API access.
|
||||
|
||||
## Service architecture
|
||||
|
||||
```
|
||||
app/src/services/
|
||||
├─ apiClient (HTTP REST)
|
||||
│ ├─ reads auth.token from Redux
|
||||
│ └─ calls VITE_BACKEND_URL (see utils/config.ts)
|
||||
├─ socketService (Socket.io)
|
||||
│ ├─ web: JS client
|
||||
│ └─ Tauri: coordinates with Rust-side socket via utils/tauriSocket.ts
|
||||
├─ coreRpcClient.ts
|
||||
│ └─ invoke('core_rpc_relay', …) → local openhuman core (JSON-RPC)
|
||||
└─ services/api/* — domain REST modules (auth, user, teams, …)
|
||||
```
|
||||
|
||||
## API Client (`services/apiClient.ts`)
|
||||
|
||||
HTTP REST client for backend communication.
|
||||
|
||||
### Features
|
||||
|
||||
- Fetch-based implementation
|
||||
- Auto-injects JWT from Redux store
|
||||
- Typed request/response handling
|
||||
- Error handling with typed errors
|
||||
|
||||
### Usage
|
||||
|
||||
```typescript
|
||||
import apiClient from "../services/apiClient";
|
||||
|
||||
// GET request
|
||||
const user = await apiClient.get<User>("/users/me");
|
||||
|
||||
// POST request
|
||||
const result = await apiClient.post<LoginResponse>("/auth/login", {
|
||||
email,
|
||||
password,
|
||||
});
|
||||
|
||||
// With custom headers
|
||||
const data = await apiClient.get<Data>("/endpoint", {
|
||||
headers: { "X-Custom": "value" },
|
||||
});
|
||||
```
|
||||
|
||||
### Configuration
|
||||
|
||||
Reads `VITE_BACKEND_URL` from environment or uses default:
|
||||
|
||||
```typescript
|
||||
const BACKEND_URL =
|
||||
import.meta.env.VITE_BACKEND_URL || "https://api.example.com";
|
||||
```
|
||||
|
||||
## API Endpoints (`services/api/`)
|
||||
|
||||
### Auth API (`services/api/authApi.ts`)
|
||||
|
||||
Authentication-related endpoints.
|
||||
|
||||
```typescript
|
||||
import { authApi } from "../services/api/authApi";
|
||||
|
||||
// Login
|
||||
const { token, user } = await authApi.login(credentials);
|
||||
|
||||
// Token exchange (for deep link flow)
|
||||
const { sessionToken, user } = await authApi.exchangeToken(loginToken);
|
||||
|
||||
// Logout
|
||||
await authApi.logout();
|
||||
```
|
||||
|
||||
### User API (`services/api/userApi.ts`)
|
||||
|
||||
User profile endpoints.
|
||||
|
||||
```typescript
|
||||
import { userApi } from "../services/api/userApi";
|
||||
|
||||
// Get current user
|
||||
const user = await userApi.getCurrentUser();
|
||||
|
||||
// Update profile
|
||||
const updated = await userApi.updateProfile({ firstName, lastName });
|
||||
|
||||
// Get settings
|
||||
const settings = await userApi.getSettings();
|
||||
```
|
||||
|
||||
## Socket Service (`services/socketService.ts`)
|
||||
|
||||
Socket.io client singleton for real-time communication.
|
||||
|
||||
### Features
|
||||
|
||||
- Singleton pattern - single connection per app
|
||||
- Auth token passed in socket `auth` object
|
||||
- Transports: polling first, then WebSocket upgrade
|
||||
- Auto-reconnection handling
|
||||
|
||||
### API
|
||||
|
||||
```typescript
|
||||
import socketService from "../services/socketService";
|
||||
|
||||
// Connect with auth token
|
||||
socketService.connect(token);
|
||||
|
||||
// Disconnect
|
||||
socketService.disconnect();
|
||||
|
||||
// Emit event
|
||||
socketService.emit("event-name", data);
|
||||
|
||||
// Listen for events
|
||||
socketService.on("event-name", (data) => {
|
||||
// Handle event
|
||||
});
|
||||
|
||||
// Remove listener
|
||||
socketService.off("event-name", handler);
|
||||
|
||||
// One-time listener
|
||||
socketService.once("event-name", (data) => {
|
||||
// Handle once
|
||||
});
|
||||
|
||||
// Get socket instance
|
||||
const socket = socketService.getSocket();
|
||||
|
||||
// Check connection status
|
||||
const isConnected = socketService.isConnected();
|
||||
```
|
||||
|
||||
### Connection Flow
|
||||
|
||||
```typescript
|
||||
// In SocketProvider.tsx
|
||||
useEffect(() => {
|
||||
if (token) {
|
||||
socketService.connect(token);
|
||||
|
||||
socketService.on("connect", () => {
|
||||
dispatch(setSocketStatus({ userId, status: "connected" }));
|
||||
dispatch(setSocketId({ userId, socketId: socket.id }));
|
||||
// Initialize MCP server
|
||||
initMCPServer(socketService.getSocket());
|
||||
});
|
||||
|
||||
socketService.on("disconnect", () => {
|
||||
dispatch(setSocketStatus({ userId, status: "disconnected" }));
|
||||
});
|
||||
}
|
||||
|
||||
return () => {
|
||||
socketService.disconnect();
|
||||
};
|
||||
}, [token]);
|
||||
```
|
||||
|
||||
### Configuration
|
||||
|
||||
```typescript
|
||||
const socket = io(BACKEND_URL, {
|
||||
auth: { token },
|
||||
transports: ["polling", "websocket"],
|
||||
reconnection: true,
|
||||
reconnectionAttempts: 5,
|
||||
reconnectionDelay: 1000,
|
||||
});
|
||||
```
|
||||
|
||||
### Socket event contract (Tauri)
|
||||
|
||||
In Tauri mode, connection and events are bridged through **`utils/tauriSocket.ts`** (`setupTauriSocketListeners`, `connectRustSocket`, etc.). See `providers/SocketProvider.tsx` for the full flow (including daemon lifecycle hooks).
|
||||
|
||||
## Core RPC (`services/coreRpcClient.ts`)
|
||||
|
||||
The desktop app runs a separate **`openhuman`** Rust binary (staged under `app/src-tauri/binaries/`). The UI calls JSON-RPC methods on that process through Tauri:
|
||||
|
||||
```typescript
|
||||
import { callCoreRpc } from "../services/coreRpcClient";
|
||||
|
||||
const result = await callCoreRpc<MyType>({
|
||||
method: "some.openhuman.method",
|
||||
params: {
|
||||
/* … */
|
||||
},
|
||||
serviceManaged: false, // true if the relay should ensure the systemd/launchd-style service
|
||||
});
|
||||
```
|
||||
|
||||
Implementation: `invoke('core_rpc_relay', { request: { method, params, serviceManaged } })` → `app/src-tauri/src/commands/core_relay.rs` → HTTP client in `app/src-tauri/src/core_rpc.rs`.
|
||||
|
||||
## Service integration with providers
|
||||
|
||||
### SocketProvider
|
||||
|
||||
`app/src/providers/SocketProvider.tsx` connects when `auth.token` is present. In **Tauri**, it prefers the Rust-backed socket path; in **web**, it uses the JS Socket.io client. See the source for logging and `useDaemonLifecycle` integration.
|
||||
|
||||
### UserProvider, AIProvider, SkillProvider
|
||||
|
||||
These wrap user profile loading, AI/memory client coordination, and skills catalog/sync. They sit **inside** `PersistGate` and **outside** or alongside the router as shown in `App.tsx`.
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Use singletons** - Never create multiple service instances
|
||||
2. **Store sessions in Redux** - Not localStorage
|
||||
3. **Clean up on unmount** - Disconnect in useEffect cleanup
|
||||
4. **Handle errors gracefully** - Retry for transient failures
|
||||
5. **Pass auth via proper channels** - Socket auth object, not query string
|
||||
|
||||
---
|
||||
|
||||
_Previous: [State Management](./02-state-management.md) | Next: [MCP System](./04-mcp-system.md)_
|
||||
@@ -1,261 +0,0 @@
|
||||
---
|
||||
description: Redux Toolkit + Redux-Persist patterns used in the React app.
|
||||
icon: database
|
||||
---
|
||||
|
||||
# State Management
|
||||
|
||||
The application uses Redux Toolkit with Redux-Persist for robust state management.
|
||||
|
||||
## Store Configuration
|
||||
|
||||
**File:** `store/index.ts`
|
||||
|
||||
```typescript
|
||||
// Combines all slices with persistence
|
||||
const persistConfig = {
|
||||
key: 'root',
|
||||
storage,
|
||||
whitelist: ['auth', 'telegram'], // Persisted slices
|
||||
};
|
||||
```
|
||||
|
||||
## Redux State Structure
|
||||
|
||||
```typescript
|
||||
RootState = {
|
||||
auth: {
|
||||
token: string | null, // JWT (persisted)
|
||||
isOnboardedByUser: Record<string, boolean>, // Per-user flag (persisted)
|
||||
},
|
||||
socket: {
|
||||
byUser: Record<
|
||||
string,
|
||||
{
|
||||
// Per user ID
|
||||
status: 'connecting' | 'connected' | 'disconnected';
|
||||
socketId: string | null;
|
||||
}
|
||||
>,
|
||||
},
|
||||
user: { profile: User | null, loading: boolean, error: string | null },
|
||||
telegram: {
|
||||
byUser: Record<string, TelegramState>, // Per Telegram user (persisted)
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
## Slices
|
||||
|
||||
### Auth Slice (`store/authSlice.ts`)
|
||||
|
||||
Manages JWT token and per-user onboarding status.
|
||||
|
||||
**State:**
|
||||
|
||||
```typescript
|
||||
interface AuthState {
|
||||
token: string | null;
|
||||
isOnboardedByUser: Record<string, boolean>;
|
||||
}
|
||||
```
|
||||
|
||||
**Actions:**
|
||||
|
||||
- `setToken(token: string)` - Store JWT after login
|
||||
- `clearToken()` - Remove token on logout
|
||||
- `setOnboarded({ userId, isOnboarded })` - Mark user as onboarded
|
||||
|
||||
**Selectors (`store/authSelectors.ts`):**
|
||||
|
||||
- `selectToken` - Get current JWT
|
||||
- `selectIsOnboarded(userId)` - Check if user completed onboarding
|
||||
|
||||
### Socket Slice (`store/socketSlice.ts`)
|
||||
|
||||
Tracks Socket.io connection status per user.
|
||||
|
||||
**State:**
|
||||
|
||||
```typescript
|
||||
interface SocketState {
|
||||
byUser: Record<
|
||||
string,
|
||||
{ status: 'connecting' | 'connected' | 'disconnected'; socketId: string | null }
|
||||
>;
|
||||
}
|
||||
```
|
||||
|
||||
**Actions:**
|
||||
|
||||
- `setSocketStatus({ userId, status })` - Update connection status
|
||||
- `setSocketId({ userId, socketId })` - Store socket ID
|
||||
- `clearSocketState(userId)` - Clear user's socket state
|
||||
|
||||
**Selectors (`store/socketSelectors.ts`):**
|
||||
|
||||
- `selectSocketStatus(userId)` - Get connection status
|
||||
- `selectIsSocketConnected(userId)` - Boolean connected check
|
||||
|
||||
### User Slice (`store/userSlice.ts`)
|
||||
|
||||
Stores user profile data.
|
||||
|
||||
**State:**
|
||||
|
||||
```typescript
|
||||
interface UserState {
|
||||
profile: User | null;
|
||||
loading: boolean;
|
||||
error: string | null;
|
||||
}
|
||||
```
|
||||
|
||||
**Actions:**
|
||||
|
||||
- `setUser(user)` - Store user profile
|
||||
- `setUserLoading(loading)` - Set loading state
|
||||
- `setUserError(error)` - Set error state
|
||||
- `clearUser()` - Clear profile on logout
|
||||
|
||||
### Telegram Slice (`store/telegram/`)
|
||||
|
||||
Complex nested state management for Telegram integration.
|
||||
|
||||
**Files:**
|
||||
|
||||
- `index.ts` - Slice exports (actions, thunks)
|
||||
- `types.ts` - Entity and state interfaces
|
||||
- `reducers.ts` - Synchronous reducers
|
||||
- `extraReducers.ts` - Async thunk handlers
|
||||
- `thunks.ts` - Async operations
|
||||
|
||||
**State Structure:**
|
||||
|
||||
```typescript
|
||||
telegram.byUser[telegramUserId] = {
|
||||
connectionStatus: "disconnected" | "connecting" | "connected" | "error",
|
||||
authStatus: "not_authenticated" | "authenticating" | "authenticated" | "error",
|
||||
currentUser: TelegramUser | null,
|
||||
sessionString: string | null, // Stored here, NOT localStorage
|
||||
chats: Record<string, TelegramChat>,
|
||||
chatsOrder: string[],
|
||||
messages: Record<chatId, Record<msgId, TelegramMessage>>,
|
||||
threads: Record<chatId, TelegramThread[]>
|
||||
}
|
||||
```
|
||||
|
||||
**Reducers:**
|
||||
|
||||
- `setCurrentUser` - Store authenticated Telegram user
|
||||
- `setSessionString` - Store MTProto session (for persistence)
|
||||
- `setConnectionStatus` - Update connection state
|
||||
- `setAuthStatus` - Update authentication state
|
||||
- `addChat` / `updateChat` - Manage chat list
|
||||
- `addMessage` / `updateMessage` - Manage message history
|
||||
- `setThreads` - Store thread data
|
||||
|
||||
**Thunks (`store/telegram/thunks.ts`):**
|
||||
|
||||
- `initializeTelegram(userId)` - Initialize MTProto client
|
||||
- `connectTelegram(userId)` - Establish Telegram connection
|
||||
- `fetchChats(userId)` - Load chat list
|
||||
- `fetchMessages({ userId, chatId })` - Load message history
|
||||
- `disconnectTelegram(userId)` - Clean disconnect
|
||||
|
||||
**Selectors (`store/telegramSelectors.ts`):**
|
||||
|
||||
- `selectTelegramState(userId)` - Get full Telegram state
|
||||
- `selectTelegramConnectionStatus(userId)` - Get connection status
|
||||
- `selectTelegramAuthStatus(userId)` - Get auth status
|
||||
- `selectTelegramChats(userId)` - Get chat list
|
||||
- `selectTelegramMessages(userId, chatId)` - Get messages for chat
|
||||
|
||||
## Typed Hooks
|
||||
|
||||
**File:** `store/hooks.ts`
|
||||
|
||||
```typescript
|
||||
// Use these instead of plain useDispatch/useSelector
|
||||
export const useAppDispatch: () => AppDispatch = useDispatch;
|
||||
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
|
||||
```
|
||||
|
||||
## Persistence Configuration
|
||||
|
||||
### What's Persisted
|
||||
|
||||
- `auth.token` - JWT for authentication
|
||||
- `auth.isOnboardedByUser` - Per-user onboarding status
|
||||
- `telegram.byUser` - Telegram state (sessions, chats, etc.)
|
||||
|
||||
### What's NOT Persisted
|
||||
|
||||
- `socket` - Connection state (reconnects on app start)
|
||||
- `user.loading` / `user.error` - Transient UI states
|
||||
- Telegram loading/error states
|
||||
|
||||
### Storage Backend
|
||||
|
||||
Redux-Persist uses localStorage adapter by default. This is the ONLY acceptable use of localStorage in the application.
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Reading State
|
||||
|
||||
```typescript
|
||||
import { useAppSelector } from '../store/hooks';
|
||||
|
||||
function MyComponent() {
|
||||
const token = useAppSelector(state => state.auth.token);
|
||||
const isConnected = useAppSelector(state => state.socket.byUser[userId]?.status === 'connected');
|
||||
const chats = useAppSelector(state => state.telegram.byUser[userId]?.chats);
|
||||
}
|
||||
```
|
||||
|
||||
### Dispatching Actions
|
||||
|
||||
```typescript
|
||||
import { clearToken, setToken } from '../store/authSlice';
|
||||
import { useAppDispatch } from '../store/hooks';
|
||||
import { initializeTelegram } from '../store/telegram/thunks';
|
||||
|
||||
function MyComponent() {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
// Sync action
|
||||
const handleLogin = (token: string) => {
|
||||
dispatch(setToken(token));
|
||||
};
|
||||
|
||||
// Async thunk
|
||||
const handleConnect = async () => {
|
||||
await dispatch(initializeTelegram(userId)).unwrap();
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### Using Selectors
|
||||
|
||||
```typescript
|
||||
import { selectIsOnboarded } from '../store/authSelectors';
|
||||
import { useAppSelector } from '../store/hooks';
|
||||
import { selectTelegramConnectionStatus } from '../store/telegramSelectors';
|
||||
|
||||
function MyComponent({ userId }) {
|
||||
const isOnboarded = useAppSelector(state => selectIsOnboarded(state, userId));
|
||||
const connectionStatus = useAppSelector(state => selectTelegramConnectionStatus(state, userId));
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Always use typed hooks** - `useAppDispatch` and `useAppSelector`
|
||||
2. **Use selectors for derived state** - Memoized and testable
|
||||
3. **Keep thunks in separate files** - Better organization
|
||||
4. **Per-user state scoping** - Key state by user ID
|
||||
5. **Avoid localStorage** - Use Redux-Persist instead
|
||||
|
||||
---
|
||||
|
||||
_Previous: [Architecture Overview](./01-architecture.md) | Next: [Services Layer](./03-services.md)_
|
||||
@@ -182,7 +182,7 @@ pkill -f "openhuman-core"
|
||||
pnpm dev:app
|
||||
```
|
||||
|
||||
If the lock is left behind by a crashed process (PID no longer alive), the preflight removes the stale `SingletonLock` automatically and dev startup proceeds — no manual cleanup required.
|
||||
If the lock is left behind by a crashed process (PID no longer alive), the preflight removes the stale `SingletonLock` automatically and dev startup proceeds, no manual cleanup required.
|
||||
|
||||
**Known limitation**
|
||||
|
||||
@@ -192,7 +192,7 @@ Dev and release builds still share `com.openhuman.app` as the cache identifier.
|
||||
|
||||
**Symptom**
|
||||
|
||||
A previous Tauri build or `openhuman-core run` harness left a process listening on `OPENHUMAN_CORE_PORT` (default `7788`). Until issue #1130 the new Tauri build would silently attach to that listener — leading to version drift and 401s when the new build's `OPENHUMAN_CORE_TOKEN` didn't match.
|
||||
A previous Tauri build or `openhuman-core run` harness left a process listening on `OPENHUMAN_CORE_PORT` (default `7788`). Until issue #1130 the new Tauri build would silently attach to that listener, leading to version drift and 401s when the new build's `OPENHUMAN_CORE_TOKEN` didn't match.
|
||||
|
||||
**Current behavior (issue #1130)**
|
||||
|
||||
@@ -200,7 +200,7 @@ A previous Tauri build or `openhuman-core run` harness left a process listening
|
||||
|
||||
- If `GET /` identifies the listener as an OpenHuman core (JSON body with `"name": "openhuman"`), it is treated as a stale process from a previous run and proactively terminated (`SIGTERM`, then `SIGKILL` after 750ms on Unix; `taskkill /F /T /PID` on Windows). The Tauri host then spawns its own fresh embedded core.
|
||||
- If the listener is something else (or doesn't speak HTTP), startup fails loudly with the conflict surfaced in the log instead of silently attaching.
|
||||
- Set `OPENHUMAN_CORE_REUSE_EXISTING=1` to opt back into the legacy attach-to-anything behavior — useful when running `openhuman-core run` as a manual debugging harness.
|
||||
- Set `OPENHUMAN_CORE_REUSE_EXISTING=1` to opt back into the legacy attach-to-anything behavior, useful when running `openhuman-core run` as a manual debugging harness.
|
||||
|
||||
**Manual cleanup (still works)**
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ Implementation: `app/src/utils/oauthAppVersionGate.ts`, `app/src/utils/desktopDe
|
||||
2. When dropping support for older installs, set **`VITE_MINIMUM_SUPPORTED_APP_VERSION`** to the new floor **before** or **with** that release (repo Actions variables + both workflow steps above).
|
||||
3. Remove, redirect, or retire older stable installers and stale **updater** entries from user-facing surfaces (GitHub Release assets, website, CDN, updater feed). Confirm deprecated artifacts are not reachable from default install/update flows.
|
||||
4. Smoke-test **Gmail connect** on a fresh install from **releases/latest**.
|
||||
5. Complete the [manual smoke checklist](./RELEASE-MANUAL-SMOKE.md), then paste the completed sign-off block (verbatim, with every checked item left checked) into the release PR description before tagging.
|
||||
5. Complete the [manual smoke checklist](../../docs/RELEASE-MANUAL-SMOKE.md), then paste the completed sign-off block (verbatim, with every checked item left checked) into the release PR description before tagging.
|
||||
|
||||
## Workflows: staging vs. production
|
||||
|
||||
@@ -45,10 +45,10 @@ Two first-class GitHub Actions workflows, one per environment. Pick by intent ra
|
||||
|
||||
| Workflow | Branch | Bumps | Tags pushed | Concurrency group | Use when |
|
||||
| ------------------------------------------------------- | --------- | ------- | -------------------------- | ----------------------- | --------------------------------------------------------------------- |
|
||||
| [`release-staging.yml`](../.github/workflows/release-staging.yml) | `main` | `patch` only | `v<version>-staging` | `release-staging` | Cutting a staging build for QA. Runs frequently; narrow semver moves. |
|
||||
| [`release-production.yml`](../.github/workflows/release-production.yml) | `main` | `patch` / `minor` / `major` (only on `main_head`) | `v<version>` | `release-production` | Promoting a validated staging tag, or hotfixing from `main` HEAD. |
|
||||
| [`release-staging.yml`](../../.github/workflows/release-staging.yml) | `main` | `patch` only | `v<version>-staging` | `release-staging` | Cutting a staging build for QA. Runs frequently; narrow semver moves. |
|
||||
| [`release-production.yml`](../../.github/workflows/release-production.yml) | `main` | `patch` / `minor` / `major` (only on `main_head`) | `v<version>` | `release-production` | Promoting a validated staging tag, or hotfixing from `main` HEAD. |
|
||||
|
||||
The matrix build / sign / Sentry-DIF / artifact-upload pipeline used by both flows lives in [`.github/workflows/build-desktop.yml`](../.github/workflows/build-desktop.yml) as a `workflow_call` reusable workflow. The two top-level workflows above own ref resolution, version bumping, tagging, and publish/cleanup; the build itself is shared.
|
||||
The matrix build / sign / Sentry-DIF / artifact-upload pipeline used by both flows lives in [`.github/workflows/build-desktop.yml`](../../.github/workflows/build-desktop.yml) as a `workflow_call` reusable workflow. The two top-level workflows above own ref resolution, version bumping, tagging, and publish/cleanup; the build itself is shared.
|
||||
|
||||
### Cutting a staging build
|
||||
|
||||
@@ -57,13 +57,13 @@ The matrix build / sign / Sentry-DIF / artifact-upload pipeline used by both flo
|
||||
3. Build matrix runs from the **tag** (not main HEAD), so reruns rebuild byte-identical content even if `main` has moved on.
|
||||
4. On failure the staging tag is auto-deleted; the bump commit on `main` stays so the next cut continues from `vX.Y.(Z+1)`.
|
||||
|
||||
There is no separate `staging` branch — staging cuts and production promotions both live on `main`. The two are distinguished only by tag suffix (`-staging` vs none) and by which workflow created the tag.
|
||||
There is no separate `staging` branch, staging cuts and production promotions both live on `main`. The two are distinguished only by tag suffix (`-staging` vs none) and by which workflow created the tag.
|
||||
|
||||
### Promoting to production (default flow)
|
||||
|
||||
1. Run **Release Production** via `workflow_dispatch` with `release_source = staging_tag` (the default).
|
||||
2. Leave `staging_tag` blank to promote the latest `v*-staging`, or pass an explicit tag (e.g. `v1.2.4-staging`) to pin.
|
||||
3. The workflow strips `-staging`, creates `v<version>` at the same commit, and runs the production build matrix from that tag. **No further version bump** — the artifact reuses what staging already validated.
|
||||
3. The workflow strips `-staging`, creates `v<version>` at the same commit, and runs the production build matrix from that tag. **No further version bump**, the artifact reuses what staging already validated.
|
||||
|
||||
### Hotfix from `main` HEAD
|
||||
|
||||
|
||||
@@ -0,0 +1,211 @@
|
||||
---
|
||||
description: The desktop host (`app/src-tauri/`) — Tauri v2 + WebView, IPC, sidecar lifecycle, core bridge.
|
||||
icon: desktop
|
||||
---
|
||||
|
||||
# Tauri shell (`app/src-tauri/`)
|
||||
|
||||
The desktop host for OpenHuman: Tauri v2 + WebView, IPC commands, window management, and bridging to the `openhuman` Rust sidecar (core JSON-RPC). It does **not** duplicate the full domain stack, that lives in the repo-root Rust crate (`openhuman_core`, `src/bin/openhuman.rs`).
|
||||
|
||||
## Responsibilities
|
||||
|
||||
1. **Web UI**. Load the Vite build from `app/dist` (or dev server on port 1420).
|
||||
2. **IPC**. Expose a small, explicit set of Tauri commands (see [Commands](#commands)).
|
||||
3. **Core lifecycle**. Ensure the `openhuman` binary is running (child process and/or service) and proxy JSON-RPC via `core_rpc_relay`.
|
||||
4. **AI prompts on disk**. Resolve bundled `src/openhuman/agent/prompts` from resources / dev cwd for `ai_get_config` / `write_ai_config_file`.
|
||||
5. **Window + tray**. Desktop window behavior and system tray (see `lib.rs`).
|
||||
|
||||
## Building the sidecar
|
||||
|
||||
`app/package.json` `core:stage` runs `scripts/stage-core-sidecar.mjs`, which `cargo build --bin openhuman` at the repo root and copies the binary into `app/src-tauri/binaries/` for Tauri `externalBin`.
|
||||
|
||||
## Stuck process recovery
|
||||
|
||||
Normal app quit runs teardown from `RunEvent::ExitRequested`: child webviews are closed before CEF shutdown, the embedded core's cancellation token is triggered, and the final process sweep sends `SIGTERM` to direct children before escalating holdouts with `SIGKILL` after a short grace period. Sweep summaries are logged as `[app] sweep: term=N kill=M total=K`; any nonzero `kill` count is a warning and means a child ignored graceful shutdown.
|
||||
|
||||
On macOS, hard exits (Force Quit, `SIGKILL`, renderer crash) can skip normal teardown. The next launch runs startup recovery before CEF cache preflight: it lists OpenHuman processes whose executable path belongs to the launching `.app/Contents`, skips the current process, sends `SIGTERM`, waits briefly, then `SIGKILL`s stragglers that still match the same pid+command. Logs use the `[startup-recovery]` prefix.
|
||||
|
||||
Startup recovery skips when `OPENHUMAN_CORE_REUSE_EXISTING=1` is set (so manual CLI-core reuse still works) and when the CEF `SingletonLock` is held by a live process (so the normal second-instance path can fail without killing the already-running app). The Tauri command `process_diagnostics_list_owned` returns the currently owned process list; the macOS implementation is bundle-scoped, Linux/Windows currently return empty.
|
||||
|
||||
|
||||
## Tauri shell architecture (`app/src-tauri/`)
|
||||
|
||||
### Overview
|
||||
|
||||
The **`app/src-tauri`** crate (Rust package **`OpenHuman`**, binary **`OpenHuman`**) is a **desktop-only** host. It embeds the React UI, registers plugins (deep link, opener, OS, notifications, autostart, updater), manages the main window and tray, and **relays JSON-RPC** to the separately built **`openhuman`** core binary.
|
||||
|
||||
Non-desktop targets fail at compile time (`compile_error!` in `lib.rs`).
|
||||
|
||||
### Directory layout (actual)
|
||||
|
||||
```
|
||||
app/src-tauri/src/
|
||||
├── lib.rs # `run()`, tray/menu actions, plugins, `generate_handler!`, core startup
|
||||
├── main.rs # Binary entry
|
||||
├── core_process.rs # CoreProcessHandle, spawn/monitor openhuman sidecar
|
||||
├── core_rpc.rs # HTTP client to core JSON-RPC
|
||||
├── commands/
|
||||
│ ├── mod.rs # Re-exports
|
||||
│ ├── core_relay.rs # `core_rpc_relay`, service-managed core bootstrap
|
||||
│ ├── openhuman.rs # Daemon host config, systemd-style service helpers
|
||||
│ └── window.rs # show/hide/minimize/close window
|
||||
└── utils/
|
||||
├── mod.rs
|
||||
└── dev_paths.rs # Resolve bundled AI prompts paths
|
||||
```
|
||||
|
||||
There is **no** `src-tauri/src/services/session_service.rs` in this tree; session semantics are handled in the web layer + backend + core as applicable.
|
||||
|
||||
### Data flow: UI → core
|
||||
|
||||
```
|
||||
React (invoke)
|
||||
→ core_rpc_relay { method, params, serviceManaged? }
|
||||
→ core_rpc::call HTTP POST to OPENHUMAN_CORE_RPC_URL
|
||||
→ openhuman binary (src/bin/openhuman.rs → core_server)
|
||||
```
|
||||
|
||||
`CoreProcessHandle` in `core_process.rs` starts or waits for the sidecar; `commands/core_relay.rs` optionally ensures a **service-managed** core is running before relaying.
|
||||
|
||||
### Window and tray behavior
|
||||
|
||||
- The shell creates a tray icon at startup and wires actions to open the main window or quit.
|
||||
- In daemon mode (`daemon` / `--daemon`), the main window is hidden on launch and can be reopened from tray actions.
|
||||
- On macOS `RunEvent::Reopen` also restores and focuses the main window.
|
||||
- Windows and Linux use the same tray actions (`Open OpenHuman`, `Quit`), with desktop-environment-specific tray rendering differences on some Linux setups.
|
||||
|
||||
### Bundled resources
|
||||
|
||||
`tauri.conf.json` bundles **`../../skills/skills`** and **`../../src/openhuman/agent/prompts`** so skills and prompt markdown ship with the app.
|
||||
|
||||
### Related
|
||||
|
||||
- IPC surface: see the [Commands](#tauri-ipc-commands-app-src-tauri) section below
|
||||
- HTTP bridge: see the [Core bridge & helpers](#core-bridge-helpers-app-src-tauri) section below
|
||||
- Rust domains (implementation): repo root `src/openhuman/`, `src/core_server/`
|
||||
|
||||
|
||||
## Tauri IPC commands (`app/src-tauri`)
|
||||
|
||||
All commands are registered in **`app/src-tauri/src/lib.rs`** inside `tauri::generate_handler![...]` (desktop build). Names below are the **Rust** command names (camelCase in JS via serde where applicable).
|
||||
|
||||
### Demo / diagnostics
|
||||
|
||||
| Command | Purpose |
|
||||
| ------- | ------------------------------------------ |
|
||||
| `greet` | Demo string (safe to remove in production) |
|
||||
|
||||
### AI configuration (bundled prompts)
|
||||
|
||||
| Command | Purpose |
|
||||
| ---------------------- | -------------------------------------------------------------------------------------------- |
|
||||
| `ai_get_config` | Build `AIPreview` from resolved `SOUL.md` / `TOOLS.md` under bundled or dev `src/openhuman/agent/prompts` |
|
||||
| `ai_refresh_config` | Same read path as `ai_get_config` (refresh hook) |
|
||||
| `write_ai_config_file` | Write a single `.md` under repo `src/openhuman/agent/prompts` (dev / safe filename checks) |
|
||||
|
||||
### Core JSON-RPC relay
|
||||
|
||||
| Command | Purpose |
|
||||
| ---------------- | -------------------------------------------------------------------------------------------------------------- |
|
||||
| `core_rpc_relay` | Body: `{ method, params?, serviceManaged? }` → forwards to local **`openhuman`** HTTP JSON-RPC (`core_rpc.rs`) |
|
||||
|
||||
Use **`app/src/services/coreRpcClient.ts`** (`callCoreRpc`) from the frontend.
|
||||
|
||||
### Window management
|
||||
|
||||
From **`commands/window.rs`** (names may vary slightly; see `lib.rs`):
|
||||
|
||||
| Command | Purpose |
|
||||
| ------------------- | ----------------- |
|
||||
| `show_window` | Show main window |
|
||||
| `hide_window` | Hide main window |
|
||||
| `toggle_window` | Toggle visibility |
|
||||
| `is_window_visible` | Query visibility |
|
||||
| `minimize_window` | Minimize |
|
||||
| `maximize_window` | Maximize |
|
||||
| `close_window` | Close |
|
||||
| `set_window_title` | Set title string |
|
||||
|
||||
### OpenHuman daemon / service helpers
|
||||
|
||||
From **`commands/openhuman.rs`** (see source for exact payloads):
|
||||
|
||||
| Command | Purpose |
|
||||
| ---------------------------------- | ---------------------------------------------- |
|
||||
| `openhuman_get_daemon_host_config` | Read daemon host preferences (e.g. tray) |
|
||||
| `openhuman_set_daemon_host_config` | Persist daemon host preferences |
|
||||
| `openhuman_service_install` | Install background service (platform-specific) |
|
||||
| `openhuman_service_start` | Start service |
|
||||
| `openhuman_service_stop` | Stop service |
|
||||
| `openhuman_service_status` | Query status |
|
||||
| `openhuman_service_uninstall` | Uninstall service |
|
||||
|
||||
### Screen share picker (CEF / macOS)
|
||||
|
||||
From **`screen_capture/mod.rs`**. Backs the in-page `getDisplayMedia` shim in `webview_accounts/runtime.js`. Session-gated: the shim must open a session with a live user gesture before enumeration / thumbnail captures succeed. See issue #713 (picker UX) + #812 (session gating).
|
||||
|
||||
| Command | Purpose |
|
||||
| --------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
|
||||
| `screen_share_begin_session` | Open a 30s session from an account webview, after a `navigator.userActivation.isActive` gesture. Returns `{ token, sources }`. Rate-limited to 10/minute per account. |
|
||||
| `screen_share_thumbnail` | Capture a single source's thumbnail as base64 PNG. Requires a live token and an `id` that the session was issued for. macOS only; other platforms return an error. |
|
||||
| `screen_share_finalize_session` | Close the session. Called by the shim on Share or Cancel; safe to call with an unknown/expired token (no-op). |
|
||||
|
||||
### Removed / not present
|
||||
|
||||
The following **do not** exist in the current `generate_handler!` list: `exchange_token`, `get_auth_state`, `socket_connect`, `start_telegram_login`. Authentication and sockets are handled in the **React** app and **core** process, not via these IPC names.
|
||||
|
||||
### Example: core RPC
|
||||
|
||||
```typescript
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
|
||||
const result = await invoke("core_rpc_relay", {
|
||||
request: {
|
||||
method: "your.rpc.method",
|
||||
params: { foo: "bar" },
|
||||
serviceManaged: false,
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
_See `app/src-tauri/src/lib.rs` for the authoritative list._
|
||||
|
||||
|
||||
## Core bridge & helpers (`app/src-tauri`)
|
||||
|
||||
This document replaces the old “SessionService / SocketService” split. The Tauri crate **does not** embed a duplicate Socket.io server or Telegram client; instead it focuses on **process management** and **HTTP JSON-RPC** to the **`openhuman`** binary.
|
||||
|
||||
### `CoreProcessHandle` (`core_process.rs`)
|
||||
|
||||
- Resolves the **`openhuman`** executable (staged under `binaries/` or `PATH` / dev layout).
|
||||
- Starts or attaches to the core process and exposes its RPC URL (`OPENHUMAN_CORE_RPC_URL`).
|
||||
- Used during app setup in `lib.rs` (`app.manage(core_handle)`).
|
||||
|
||||
### `core_rpc` (`core_rpc.rs`)
|
||||
|
||||
- HTTP client for the core’s JSON-RPC surface (localhost).
|
||||
- Used by **`core_rpc_relay`** to forward `method` + `params` from the frontend.
|
||||
|
||||
### `commands/core_relay.rs`
|
||||
|
||||
- **`core_rpc_relay`**. ensures the core is running (in-process handle or **service-managed** path), then calls `core_rpc`.
|
||||
- **`ensure_service_managed_core_running`**. bootstraps systemd/launchd-style service when RPC is down (platform-specific behavior inside core CLI).
|
||||
|
||||
### `commands/openhuman.rs`
|
||||
|
||||
- Daemon host JSON config (e.g. tray visibility) under the app data directory.
|
||||
- Install/start/stop/status/uninstall helpers for the **openhuman** background service.
|
||||
|
||||
### `utils/dev_paths.rs`
|
||||
|
||||
- Resolves **`src/openhuman/agent/prompts`** for development and bundled resource paths for AI preview.
|
||||
|
||||
### `utils/tauriSocket.ts` (frontend)
|
||||
|
||||
Not in `src-tauri`, but **pairs** with the shell: the React app listens for Tauri events that mirror socket activity when using the Rust-side client. See `app/src/utils/tauriSocket.ts` and the [Frontend Services](frontend.md#services-layer) chapter.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
---
|
||||
description: Index for the desktop host (app/src-tauri/) — Tauri v2, IPC, sidecar lifecycle.
|
||||
icon: desktop
|
||||
---
|
||||
|
||||
# Tauri shell documentation (`app/src-tauri/`)
|
||||
|
||||
This folder is the **desktop host** for OpenHuman: Tauri v2 + WebView, IPC commands, window management, and bridging to the **`openhuman`** Rust sidecar (core JSON-RPC). It does **not** duplicate the full domain stack — that lives in the repo-root Rust crate (`openhuman_core`, `src/bin/openhuman.rs`).
|
||||
|
||||
## Quick reference
|
||||
|
||||
| Document | Description |
|
||||
| ------------------------------------ | ------------------------------------------ |
|
||||
| [Architecture](./01-architecture.md) | Modules, core process, sidecar staging |
|
||||
| [IPC commands](./02-commands.md) | `invoke` commands registered in `lib.rs` |
|
||||
| [Core bridge](./03-services.md) | `core_process`, `core_rpc`, daemon helpers |
|
||||
|
||||
## Responsibilities
|
||||
|
||||
1. **Web UI** — Load the Vite build from `app/dist` (or dev server on port 1420).
|
||||
2. **IPC** — Expose a small, explicit set of Tauri commands (see `02-commands.md`).
|
||||
3. **Core lifecycle** — Ensure the **`openhuman`** binary is running (child process and/or service) and proxy JSON-RPC via **`core_rpc_relay`**.
|
||||
4. **AI prompts on disk** — Resolve bundled `src/openhuman/agent/prompts` from resources / dev cwd for `ai_get_config` / `write_ai_config_file`.
|
||||
5. **Window + tray** — Desktop window behavior and system tray (see `lib.rs`).
|
||||
|
||||
## Building the sidecar
|
||||
|
||||
`app/package.json` **`core:stage`** runs `scripts/stage-core-sidecar.mjs`, which `cargo build --bin openhuman` at the repo root and copies the binary into `app/src-tauri/binaries/` for Tauri `externalBin`.
|
||||
|
||||
## Stuck process recovery
|
||||
|
||||
Normal app quit runs teardown from `RunEvent::ExitRequested`: child webviews are closed before CEF shutdown, the embedded core's cancellation token is triggered, and the final process sweep sends `SIGTERM` to direct children before escalating holdouts with `SIGKILL` after a short grace period. Sweep summaries are logged as `[app] sweep: term=N kill=M total=K`; any nonzero `kill` count is a warning and means a child ignored graceful shutdown.
|
||||
|
||||
On macOS, hard exits such as Force Quit, `SIGKILL`, or a renderer crash can skip normal teardown. The next launch runs startup recovery before CEF cache preflight: it lists OpenHuman processes whose executable path belongs to the launching `.app/Contents`, skips the current process, sends `SIGTERM`, waits briefly, then sends `SIGKILL` to stragglers that still match the same pid and command. These events use the `[startup-recovery]` log prefix.
|
||||
|
||||
Startup recovery intentionally skips when `OPENHUMAN_CORE_REUSE_EXISTING=1` is set so manual CLI-core reuse still works. It also skips when the CEF `SingletonLock` is held by a live process, letting the normal second-instance path fail without killing the already-running app.
|
||||
|
||||
For diagnostics, the Tauri command `process_diagnostics_list_owned` returns the currently owned process list used by startup recovery, or an error if process enumeration fails. The macOS implementation is bundle-scoped; Linux and Windows currently return an empty list.
|
||||
|
||||
## Related
|
||||
|
||||
- Full stack narrative: [`../ARCHITECTURE.md`](../ARCHITECTURE.md)
|
||||
- Frontend: [`../src/README.md`](../src/README.md)
|
||||
@@ -1,60 +0,0 @@
|
||||
---
|
||||
description: Architecture of the Tauri desktop host — process model, window management, sidecar.
|
||||
icon: code-branch
|
||||
---
|
||||
|
||||
# Tauri shell architecture (`app/src-tauri/`)
|
||||
|
||||
## Overview
|
||||
|
||||
The **`app/src-tauri`** crate (Rust package **`OpenHuman`**, binary **`OpenHuman`**) is a **desktop-only** host. It embeds the React UI, registers plugins (deep link, opener, OS, notifications, autostart, updater), manages the main window and tray, and **relays JSON-RPC** to the separately built **`openhuman`** core binary.
|
||||
|
||||
Non-desktop targets fail at compile time (`compile_error!` in `lib.rs`).
|
||||
|
||||
## Directory layout (actual)
|
||||
|
||||
```
|
||||
app/src-tauri/src/
|
||||
├── lib.rs # `run()`, tray/menu actions, plugins, `generate_handler!`, core startup
|
||||
├── main.rs # Binary entry
|
||||
├── core_process.rs # CoreProcessHandle, spawn/monitor openhuman sidecar
|
||||
├── core_rpc.rs # HTTP client to core JSON-RPC
|
||||
├── commands/
|
||||
│ ├── mod.rs # Re-exports
|
||||
│ ├── core_relay.rs # `core_rpc_relay`, service-managed core bootstrap
|
||||
│ ├── openhuman.rs # Daemon host config, systemd-style service helpers
|
||||
│ └── window.rs # show/hide/minimize/close window
|
||||
└── utils/
|
||||
├── mod.rs
|
||||
└── dev_paths.rs # Resolve bundled AI prompts paths
|
||||
```
|
||||
|
||||
There is **no** `src-tauri/src/services/session_service.rs` in this tree; session semantics are handled in the web layer + backend + core as applicable.
|
||||
|
||||
## Data flow: UI → core
|
||||
|
||||
```
|
||||
React (invoke)
|
||||
→ core_rpc_relay { method, params, serviceManaged? }
|
||||
→ core_rpc::call HTTP POST to OPENHUMAN_CORE_RPC_URL
|
||||
→ openhuman binary (src/bin/openhuman.rs → core_server)
|
||||
```
|
||||
|
||||
`CoreProcessHandle` in `core_process.rs` starts or waits for the sidecar; `commands/core_relay.rs` optionally ensures a **service-managed** core is running before relaying.
|
||||
|
||||
## Window and tray behavior
|
||||
|
||||
- The shell creates a tray icon at startup and wires actions to open the main window or quit.
|
||||
- In daemon mode (`daemon` / `--daemon`), the main window is hidden on launch and can be reopened from tray actions.
|
||||
- On macOS `RunEvent::Reopen` also restores and focuses the main window.
|
||||
- Windows and Linux use the same tray actions (`Open OpenHuman`, `Quit`), with desktop-environment-specific tray rendering differences on some Linux setups.
|
||||
|
||||
## Bundled resources
|
||||
|
||||
`tauri.conf.json` bundles **`../../skills/skills`** and **`../../src/openhuman/agent/prompts`** so skills and prompt markdown ship with the app.
|
||||
|
||||
## Related
|
||||
|
||||
- IPC surface: [Commands](./02-commands.md)
|
||||
- HTTP bridge: [Core bridge](./03-services.md)
|
||||
- Rust domains (implementation): repo root `src/openhuman/`, `src/core_server/`
|
||||
@@ -1,91 +0,0 @@
|
||||
---
|
||||
description: Registered Tauri IPC commands — what the frontend can invoke on the shell.
|
||||
icon: terminal
|
||||
---
|
||||
|
||||
# Tauri IPC commands (`app/src-tauri`)
|
||||
|
||||
All commands are registered in **`app/src-tauri/src/lib.rs`** inside `tauri::generate_handler![...]` (desktop build). Names below are the **Rust** command names (camelCase in JS via serde where applicable).
|
||||
|
||||
## Demo / diagnostics
|
||||
|
||||
| Command | Purpose |
|
||||
| ------- | ------------------------------------------ |
|
||||
| `greet` | Demo string (safe to remove in production) |
|
||||
|
||||
## AI configuration (bundled prompts)
|
||||
|
||||
| Command | Purpose |
|
||||
| ---------------------- | -------------------------------------------------------------------------------------------- |
|
||||
| `ai_get_config` | Build `AIPreview` from resolved `SOUL.md` / `TOOLS.md` under bundled or dev `src/openhuman/agent/prompts` |
|
||||
| `ai_refresh_config` | Same read path as `ai_get_config` (refresh hook) |
|
||||
| `write_ai_config_file` | Write a single `.md` under repo `src/openhuman/agent/prompts` (dev / safe filename checks) |
|
||||
|
||||
## Core JSON-RPC relay
|
||||
|
||||
| Command | Purpose |
|
||||
| ---------------- | -------------------------------------------------------------------------------------------------------------- |
|
||||
| `core_rpc_relay` | Body: `{ method, params?, serviceManaged? }` → forwards to local **`openhuman`** HTTP JSON-RPC (`core_rpc.rs`) |
|
||||
|
||||
Use **`app/src/services/coreRpcClient.ts`** (`callCoreRpc`) from the frontend.
|
||||
|
||||
## Window management
|
||||
|
||||
From **`commands/window.rs`** (names may vary slightly; see `lib.rs`):
|
||||
|
||||
| Command | Purpose |
|
||||
| ------------------- | ----------------- |
|
||||
| `show_window` | Show main window |
|
||||
| `hide_window` | Hide main window |
|
||||
| `toggle_window` | Toggle visibility |
|
||||
| `is_window_visible` | Query visibility |
|
||||
| `minimize_window` | Minimize |
|
||||
| `maximize_window` | Maximize |
|
||||
| `close_window` | Close |
|
||||
| `set_window_title` | Set title string |
|
||||
|
||||
## OpenHuman daemon / service helpers
|
||||
|
||||
From **`commands/openhuman.rs`** (see source for exact payloads):
|
||||
|
||||
| Command | Purpose |
|
||||
| ---------------------------------- | ---------------------------------------------- |
|
||||
| `openhuman_get_daemon_host_config` | Read daemon host preferences (e.g. tray) |
|
||||
| `openhuman_set_daemon_host_config` | Persist daemon host preferences |
|
||||
| `openhuman_service_install` | Install background service (platform-specific) |
|
||||
| `openhuman_service_start` | Start service |
|
||||
| `openhuman_service_stop` | Stop service |
|
||||
| `openhuman_service_status` | Query status |
|
||||
| `openhuman_service_uninstall` | Uninstall service |
|
||||
|
||||
## Screen share picker (CEF / macOS)
|
||||
|
||||
From **`screen_capture/mod.rs`**. Backs the in-page `getDisplayMedia` shim in `webview_accounts/runtime.js`. Session-gated: the shim must open a session with a live user gesture before enumeration / thumbnail captures succeed. See issue #713 (picker UX) + #812 (session gating).
|
||||
|
||||
| Command | Purpose |
|
||||
| --------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
|
||||
| `screen_share_begin_session` | Open a 30s session from an account webview, after a `navigator.userActivation.isActive` gesture. Returns `{ token, sources }`. Rate-limited to 10/minute per account. |
|
||||
| `screen_share_thumbnail` | Capture a single source's thumbnail as base64 PNG. Requires a live token and an `id` that the session was issued for. macOS only; other platforms return an error. |
|
||||
| `screen_share_finalize_session` | Close the session. Called by the shim on Share or Cancel; safe to call with an unknown/expired token (no-op). |
|
||||
|
||||
## Removed / not present
|
||||
|
||||
The following **do not** exist in the current `generate_handler!` list: `exchange_token`, `get_auth_state`, `socket_connect`, `start_telegram_login`. Authentication and sockets are handled in the **React** app and **core** process, not via these IPC names.
|
||||
|
||||
## Example: core RPC
|
||||
|
||||
```typescript
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
|
||||
const result = await invoke("core_rpc_relay", {
|
||||
request: {
|
||||
method: "your.rpc.method",
|
||||
params: { foo: "bar" },
|
||||
serviceManaged: false,
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
_See `app/src-tauri/src/lib.rs` for the authoritative list._
|
||||
@@ -1,41 +0,0 @@
|
||||
---
|
||||
description: How the Tauri shell talks to the openhuman core sidecar — process lifecycle and HTTP JSON-RPC.
|
||||
icon: bridge
|
||||
---
|
||||
|
||||
# Core bridge & helpers (`app/src-tauri`)
|
||||
|
||||
This document replaces the old “SessionService / SocketService” split. The Tauri crate **does not** embed a duplicate Socket.io server or Telegram client; instead it focuses on **process management** and **HTTP JSON-RPC** to the **`openhuman`** binary.
|
||||
|
||||
## `CoreProcessHandle` (`core_process.rs`)
|
||||
|
||||
- Resolves the **`openhuman`** executable (staged under `binaries/` or `PATH` / dev layout).
|
||||
- Starts or attaches to the core process and exposes its RPC URL (`OPENHUMAN_CORE_RPC_URL`).
|
||||
- Used during app setup in `lib.rs` (`app.manage(core_handle)`).
|
||||
|
||||
## `core_rpc` (`core_rpc.rs`)
|
||||
|
||||
- HTTP client for the core’s JSON-RPC surface (localhost).
|
||||
- Used by **`core_rpc_relay`** to forward `method` + `params` from the frontend.
|
||||
|
||||
## `commands/core_relay.rs`
|
||||
|
||||
- **`core_rpc_relay`** — ensures the core is running (in-process handle or **service-managed** path), then calls `core_rpc`.
|
||||
- **`ensure_service_managed_core_running`** — bootstraps systemd/launchd-style service when RPC is down (platform-specific behavior inside core CLI).
|
||||
|
||||
## `commands/openhuman.rs`
|
||||
|
||||
- Daemon host JSON config (e.g. tray visibility) under the app data directory.
|
||||
- Install/start/stop/status/uninstall helpers for the **openhuman** background service.
|
||||
|
||||
## `utils/dev_paths.rs`
|
||||
|
||||
- Resolves **`src/openhuman/agent/prompts`** for development and bundled resource paths for AI preview.
|
||||
|
||||
## `utils/tauriSocket.ts` (frontend)
|
||||
|
||||
Not in `src-tauri`, but **pairs** with the shell: the React app listens for Tauri events that mirror socket activity when using the Rust-side client. See `app/src/utils/tauriSocket.ts` and the [Frontend Services](../frontend/services.md) chapter.
|
||||
|
||||
---
|
||||
|
||||
_Previous: [Commands](./02-commands.md)_
|
||||
@@ -5,7 +5,7 @@ icon: vial
|
||||
|
||||
# Testing Strategy
|
||||
|
||||
How OpenHuman tests its product. Source of truth for "where does my test go?". Companion to [`TEST-COVERAGE-MATRIX.md`](./TEST-COVERAGE-MATRIX.md).
|
||||
How OpenHuman tests its product. Source of truth for "where does my test go?". Companion to [`TEST-COVERAGE-MATRIX.md`](../../docs/TEST-COVERAGE-MATRIX.md).
|
||||
|
||||
---
|
||||
|
||||
@@ -17,7 +17,7 @@ How OpenHuman tests its product. Source of truth for "where does my test go?". C
|
||||
| **Rust integration** | `tests/*.rs` at repo root | Full domain wiring with real Tokio runtime, mock external services, JSON-RPC end-to-end (`tests/json_rpc_e2e.rs`), domain × domain interactions | `pnpm test:rust` (which calls `bash scripts/test-rust-with-mock.sh`) |
|
||||
| **Vitest unit** | Co-located as `*.test.ts(x)` next to source under `app/src/**`, or under `app/src/**/__tests__/` | React components, hooks, store slices, pure utilities, service-layer adapters | `pnpm test:unit` |
|
||||
| **WDIO E2E** | `app/test/e2e/specs/*.spec.ts` | Full desktop flow: UI → Tauri → core sidecar → JSON-RPC; user-visible behaviour | Linux CI: `tauri-driver` (port 4444). macOS local: Appium Mac2 (port 4723). See [E2E Testing](e2e-testing.md). |
|
||||
| **Manual smoke** | [`docs/RELEASE-MANUAL-SMOKE.md`](./RELEASE-MANUAL-SMOKE.md) | OS-level surfaces drivers cannot assert: TCC permission prompts, Gatekeeper, code signing, DMG install, OS-native toasts | Human at release-cut, signed off in release PR |
|
||||
| **Manual smoke** | [`docs/RELEASE-MANUAL-SMOKE.md`](../../docs/RELEASE-MANUAL-SMOKE.md) | OS-level surfaces drivers cannot assert: TCC permission prompts, Gatekeeper, code signing, DMG install, OS-native toasts | Human at release-cut, signed off in release PR |
|
||||
|
||||
---
|
||||
|
||||
@@ -64,11 +64,11 @@ A spec that asserts only the happy path is incomplete.
|
||||
|
||||
## Determinism rules
|
||||
|
||||
- No wall-clock waits — use `waitForApp`, `waitForAppReady`, `waitForWebView` helpers, or explicit element-readiness predicates.
|
||||
- No shared filesystem state — every E2E spec runs inside an isolated `OPENHUMAN_WORKSPACE` (created/cleaned by `app/scripts/e2e-run-spec.sh`).
|
||||
- No order-dependent specs — each spec must pass when run alone.
|
||||
- No wall-clock waits, use `waitForApp`, `waitForAppReady`, `waitForWebView` helpers, or explicit element-readiness predicates.
|
||||
- No shared filesystem state, every E2E spec runs inside an isolated `OPENHUMAN_WORKSPACE` (created/cleaned by `app/scripts/e2e-run-spec.sh`).
|
||||
- No order-dependent specs, each spec must pass when run alone.
|
||||
- No reliance on absolute coordinates or animation timing.
|
||||
- No real keyboard via `browser.keys()` on tauri-driver — synthesize via `browser.execute(...)` (see `command-palette.spec.ts` for the pattern).
|
||||
- No real keyboard via `browser.keys()` on tauri-driver, synthesize via `browser.execute(...)` (see `command-palette.spec.ts` for the pattern).
|
||||
|
||||
---
|
||||
|
||||
@@ -76,11 +76,11 @@ A spec that asserts only the happy path is incomplete.
|
||||
|
||||
- **Mock backend bootstrapping**: `startMockServer` / `stopMockServer` in `app/test/e2e/mock-server.ts`.
|
||||
- **Auth shortcut**: `triggerAuthDeepLink` / `triggerAuthDeepLinkBypass` in `helpers/deep-link-helpers.ts` skips real OAuth.
|
||||
- **Element helpers**: `clickNativeButton`, `waitForWebView`, `clickToggle` in `helpers/element-helpers.ts` — use these instead of raw `XCUIElementType*` selectors.
|
||||
- **Element helpers**: `clickNativeButton`, `waitForWebView`, `clickToggle` in `helpers/element-helpers.ts`, use these instead of raw `XCUIElementType*` selectors.
|
||||
- **Shared flows**: `completeOnboardingIfVisible`, `navigateViaHash`, `navigateToSkills`, `walkOnboarding` in `helpers/shared-flows.ts`.
|
||||
- **Core RPC from spec**: `callOpenhumanRpc` in `helpers/core-rpc.ts` — drives the sidecar directly when a UI step would be brittle.
|
||||
- **Core RPC from spec**: `callOpenhumanRpc` in `helpers/core-rpc.ts`, drives the sidecar directly when a UI step would be brittle.
|
||||
- **Platform guards**: `isTauriDriver`, `isMac2`, `supportsExecuteScript` in `helpers/platform.ts`.
|
||||
- **Artifact capture on failure**: `captureFailureArtifacts` runs from `wdio.conf.ts` — screenshots + DOM dumps land under `app/test/e2e/artifacts/`.
|
||||
- **Artifact capture on failure**: `captureFailureArtifacts` runs from `wdio.conf.ts`, screenshots + DOM dumps land under `app/test/e2e/artifacts/`.
|
||||
|
||||
---
|
||||
|
||||
@@ -88,8 +88,8 @@ A spec that asserts only the happy path is incomplete.
|
||||
|
||||
- WDIO specs: `<feature-area>-flow.spec.ts` for end-to-end product flows; `<feature>.spec.ts` for narrower surfaces.
|
||||
- Vitest co-location: prefer `Component.tsx` + `Component.test.tsx` siblings; use `__tests__/` only when grouping multiple related tests.
|
||||
- Rust integration tests: snake_case file matching the surface — `<feature>_e2e.rs` for JSON-RPC-driven flows, `<feature>_integration.rs` for cross-domain.
|
||||
- Each `describe` / `mod tests` block maps to a feature-list ID range — link the matrix row in a comment if the mapping is non-obvious.
|
||||
- Rust integration tests: snake_case file matching the surface, `<feature>_e2e.rs` for JSON-RPC-driven flows, `<feature>_integration.rs` for cross-domain.
|
||||
- Each `describe` / `mod tests` block maps to a feature-list ID range, link the matrix row in a comment if the mapping is non-obvious.
|
||||
|
||||
---
|
||||
|
||||
@@ -125,7 +125,7 @@ bash app/scripts/e2e-run-spec.sh test/e2e/specs/<your-spec>.spec.ts <id>
|
||||
|
||||
## Not driver-automatable — manual smoke required
|
||||
|
||||
Some surfaces cannot be driven by WDIO / Appium because they cross OS-level trust boundaries or hardware paths. The complete checklist + sign-off block lives in [`docs/RELEASE-MANUAL-SMOKE.md`](./RELEASE-MANUAL-SMOKE.md) — that file is the source of truth for what must be verified per release. Examples of what it covers:
|
||||
Some surfaces cannot be driven by WDIO / Appium because they cross OS-level trust boundaries or hardware paths. The complete checklist + sign-off block lives in [`docs/RELEASE-MANUAL-SMOKE.md`](../../docs/RELEASE-MANUAL-SMOKE.md), that file is the source of truth for what must be verified per release. Examples of what it covers:
|
||||
|
||||
- macOS TCC permission prompts (Accessibility, Input Monitoring, Screen Recording, Microphone)
|
||||
- Gatekeeper signature validation on first launch
|
||||
@@ -134,13 +134,13 @@ Some surfaces cannot be driven by WDIO / Appium because they cross OS-level trus
|
||||
- Auto-update download + relaunch
|
||||
- OS-native notification toasts on Linux (no display server visible to the driver beyond Xvfb)
|
||||
|
||||
If a feature has no automated coverage AND is not on the manual smoke list, treat it as untested — open a coverage gap.
|
||||
If a feature has no automated coverage AND is not on the manual smoke list, treat it as untested, open a coverage gap.
|
||||
|
||||
---
|
||||
|
||||
## Coverage matrix as the contract
|
||||
|
||||
Every feature leaf in the [coverage matrix](./TEST-COVERAGE-MATRIX.md) maps to:
|
||||
Every feature leaf in the [coverage matrix](../../docs/TEST-COVERAGE-MATRIX.md) maps to:
|
||||
|
||||
1. A test path or paths, **or**
|
||||
2. A justified `🚫` with a manual-smoke entry.
|
||||
|
||||
@@ -37,22 +37,22 @@ OpenHuman is a **React + Tauri v2 desktop app** with a **Rust core** that does t
|
||||
|
||||
**Where logic lives:**
|
||||
|
||||
- **Rust core** — all business logic. Memory Tree, integrations, model routing, tools, voice. Authoritative.
|
||||
- **Tauri shell** — windowing, process lifecycle, IPC. A delivery vehicle, not where features live.
|
||||
- **React frontend** — UI and orchestration. Calls into core via JSON-RPC.
|
||||
- **Rust core**. all business logic. Memory Tree, integrations, model routing, tools, voice. Authoritative.
|
||||
- **Tauri shell**. windowing, process lifecycle, IPC. A delivery vehicle, not where features live.
|
||||
- **React frontend**. UI and orchestration. Calls into core via JSON-RPC.
|
||||
|
||||
## Data flow
|
||||
|
||||
1. **Connect** — OAuth into a [integration](../features/integrations.md). Backend stores the token; core never sees it in plaintext.
|
||||
2. **Auto-fetch** — Every five minutes the [scheduler](../features/auto-fetch.md) walks every active connection and asks each native provider to sync.
|
||||
3. **Canonicalize** — Provider output (an email page, a GitHub diff, a Slack channel dump) is normalized into provenance-tagged Markdown.
|
||||
4. **Chunk** — Markdown is split into ≤3k-token deterministic chunks.
|
||||
5. **Store** — Chunks land in SQLite (`<workspace>/memory_tree/chunks.db`) and as `.md` files in `<workspace>/wiki/`.
|
||||
6. **Score** — Background workers run embeddings, entity extraction, hotness scoring.
|
||||
7. **Summarize** — Source / topic / global summary trees are built and refreshed from the chunk pool.
|
||||
8. **Retrieve** — When you ask a question, the agent queries the Memory Tree (search / drill down / topic / global / fetch).
|
||||
9. **Compress** — Tool output and large source data go through [TokenJuice](../features/token-compression.md) before entering LLM context.
|
||||
10. **Route** — The [router](../features/model-routing.md) picks the right provider+model for the task hint.
|
||||
1. **Connect**. OAuth into a [integration](../features/integrations.md). Backend stores the token; core never sees it in plaintext.
|
||||
2. **Auto-fetch**. Every five minutes the [scheduler](../features/auto-fetch.md) walks every active connection and asks each native provider to sync.
|
||||
3. **Canonicalize**. Provider output (an email page, a GitHub diff, a Slack channel dump) is normalized into provenance-tagged Markdown.
|
||||
4. **Chunk**. Markdown is split into ≤3k-token deterministic chunks.
|
||||
5. **Store**. Chunks land in SQLite (`<workspace>/memory_tree/chunks.db`) and as `.md` files in `<workspace>/wiki/`.
|
||||
6. **Score**. Background workers run embeddings, entity extraction, hotness scoring.
|
||||
7. **Summarize**. Source / topic / global summary trees are built and refreshed from the chunk pool.
|
||||
8. **Retrieve**. When you ask a question, the agent queries the Memory Tree (search / drill down / topic / global / fetch).
|
||||
9. **Compress**. Tool output and large source data go through [TokenJuice](../features/token-compression.md) before entering LLM context.
|
||||
10. **Route**. The [router](../features/model-routing.md) picks the right provider+model for the task hint.
|
||||
|
||||
## Privacy boundary
|
||||
|
||||
@@ -69,10 +69,10 @@ Goes through the OpenHuman backend (under one subscription):
|
||||
- Integration OAuth and tool proxying.
|
||||
- TTS streaming.
|
||||
|
||||
See [Privacy & Security](../product/privacy-and-security.md) for the full picture.
|
||||
See [Privacy & Security](../features/privacy-and-security.md) for the full picture.
|
||||
|
||||
## Open source
|
||||
|
||||
- **Repo:** [github.com/tinyhumansai/openhuman](https://github.com/tinyhumansai/openhuman) — GNU GPL3.
|
||||
- **Repo:** [github.com/tinyhumansai/openhuman](https://github.com/tinyhumansai/openhuman). GNU GPL3.
|
||||
- **Issues and PRs** are welcome. The project is in early beta.
|
||||
- For contributors, the canonical developer guide is [deep architecture reference](../developing/architecture.md).
|
||||
@@ -11,7 +11,7 @@ Most "AI assistants" are reactive: you ask, they think, they answer. OpenHuman i
|
||||
|
||||
## How it works
|
||||
|
||||
A single periodic scheduler ticks every five minutes. On each tick it walks every active [integration](integrations.md), looks up the matching native provider, and — if enough time has elapsed since that connection's last sync — calls `provider.sync(ctx, SyncReason::Periodic)`.
|
||||
A single periodic scheduler ticks every five minutes. On each tick it walks every active [integration](integrations.md), looks up the matching native provider, and, if enough time has elapsed since that connection's last sync, calls `provider.sync(ctx, SyncReason::Periodic)`.
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────┐
|
||||
@@ -35,28 +35,28 @@ A single periodic scheduler ticks every five minutes. On each tick it walks ever
|
||||
A few things matter here:
|
||||
|
||||
* **One global tick, not one task per connection.** The number of connections per user is small; a single 5-minute tick is enough and keeps bookkeeping trivial.
|
||||
* **State is per `(toolkit, connection_id)`.** Each connection has its own cursor, its own last-sync timestamp, its own dedup set, its own daily budget. Restarts rebuild this from local KV — a missed periodic sync is harmless because the next tick after restart picks it back up.
|
||||
* **State is per `(toolkit, connection_id)`.** Each connection has its own cursor, its own last-sync timestamp, its own dedup set, its own daily budget. Restarts rebuild this from local KV, a missed periodic sync is harmless because the next tick after restart picks it back up.
|
||||
* **Native syncs are shared with event-driven paths.** When a webhook or `on_connection_created` event fires a non-periodic sync, it stamps the same sync\_state, so the scheduler doesn't redundantly re-fire.
|
||||
* **Errors are logged and swallowed.** The scheduler must never panic out of its loop, or periodic sync stops silently for the rest of the process lifetime.
|
||||
|
||||
## What lands in the memory tree
|
||||
|
||||
Each provider is responsible for shaping its own ingest. The Gmail provider, for example, fetches a page of new messages, runs the email canonicalizer, and pipes the result through the same `ingest` path the manual UI uses — chunks land in SQLite, summary bucket fills, topic tree gets dirtied for any entities touched.
|
||||
Each provider is responsible for shaping its own ingest. The Gmail provider, for example, fetches a page of new messages, runs the email canonicalizer, and pipes the result through the same `ingest` path the manual UI uses, chunks land in SQLite, summary bucket fills, topic tree gets dirtied for any entities touched.
|
||||
|
||||
Other providers (GitHub, Slack, Notion, …) follow the same shape: fetch new items since cursor → canonicalize → ingest into the [Memory Tree](obsidian-wiki/memory-tree.md).
|
||||
|
||||
## Why a 5-minute tick
|
||||
|
||||
The original design ran at 60 seconds. With several connected providers, that meant a steady drumbeat of HTTP fetches and DB writes — visibly busy on a laptop. Five minutes trades a little staleness for noticeably less foreground load. The per-provider `sync_interval_secs` still caps the _minimum_ delay between actual syncs; the global tick only loosens the upper bound.
|
||||
The original design ran at 60 seconds. With several connected providers, that meant a steady drumbeat of HTTP fetches and DB writes, visibly busy on a laptop. Five minutes trades a little staleness for noticeably less foreground load. The per-provider `sync_interval_secs` still caps the _minimum_ delay between actual syncs; the global tick only loosens the upper bound.
|
||||
|
||||
## Tuning and visibility
|
||||
|
||||
* **Per-provider interval** — each native provider declares its own `sync_interval_secs`, so high-traffic toolkits (Gmail) can sync more often than low-traffic ones (Stripe).
|
||||
* **Daily budget** — every connection has a daily request budget to keep API costs and rate limits sane.
|
||||
* **Logs** — sync activity is logged in the core logs at debug level.
|
||||
* **Per-provider interval**. each native provider declares its own `sync_interval_secs`, so high-traffic toolkits (Gmail) can sync more often than low-traffic ones (Stripe).
|
||||
* **Daily budget**. every connection has a daily request budget to keep API costs and rate limits sane.
|
||||
* **Logs**. sync activity is logged in the core logs at debug level.
|
||||
|
||||
## See also
|
||||
|
||||
* [Third-party Integrations](integrations.md) — the connector layer auto-fetch runs on top of.
|
||||
* [Memory Tree](obsidian-wiki/memory-tree.md) — where everything ends up.
|
||||
* [Smart Token Compression](token-compression.md) — what keeps "fetch everything" cheap.
|
||||
* [Third-party Integrations](integrations.md). the connector layer auto-fetch runs on top of.
|
||||
* [Memory Tree](obsidian-wiki/memory-tree.md). where everything ends up.
|
||||
* [Smart Token Compression](token-compression.md). what keeps "fetch everything" cheap.
|
||||
|
||||
@@ -7,15 +7,15 @@ icon: plug
|
||||
|
||||
# Third-party Integrations (118+)
|
||||
|
||||
OpenHuman ships with backend-proxied access to **118+ third-party services**. Connecting any of them is a one-click OAuth flow inside the app — there are no API keys to wire by hand, and no plugin marketplace to navigate.
|
||||
OpenHuman ships with backend-proxied access to **118+ third-party services**. Connecting any of them is a one-click OAuth flow inside the app, there are no API keys to wire by hand, and no plugin marketplace to navigate.
|
||||
|
||||
(Under the hood, the connector layer is powered by [Composio](https://composio.dev). You will not need to think about it.)
|
||||
|
||||
Once a service is connected, it shows up in three places at once:
|
||||
|
||||
1. As an **agent tool** — the model can call it directly.
|
||||
2. As a **memory source** — [auto-fetch](auto-fetch.md) syncs it into the [Memory Tree](obsidian-wiki/memory-tree.md) every five minutes.
|
||||
3. As a **profile signal** — your activity across services feeds your personalization.
|
||||
1. As an **agent tool**, the model can call it directly.
|
||||
2. As a **memory source**, [auto-fetch](auto-fetch.md) syncs it into the [Memory Tree](obsidian-wiki/memory-tree.md) every five minutes.
|
||||
3. As a **profile signal**, your activity across services feeds your personalization.
|
||||
|
||||
## Some of what's in the catalog
|
||||
|
||||
@@ -34,14 +34,48 @@ The catalog spans productivity, business, social, messaging and Google. A non-ex
|
||||
|
||||
## Native vs proxied
|
||||
|
||||
Some services have **native providers** — Rust modules that know how to ingest the service into the Memory Tree directly (e.g. Gmail's native ingest path). Others are exposed as **proxied tools** only: the agent can call them, but there's no automatic ingest yet. New native providers are added as features land.
|
||||
Some services have **native providers**. Rust modules that know how to ingest the service into the Memory Tree directly (e.g. Gmail's native ingest path). Others are exposed as **proxied tools** only: the agent can call them, but there's no automatic ingest yet. New native providers are added as features land.
|
||||
|
||||
## How connections work
|
||||
|
||||
Click **Connect** on any integration. A browser window opens for OAuth. Once you sign in, the connection becomes active and OpenHuman starts syncing it through [auto-fetch](auto-fetch.md) on the next 5-minute tick.
|
||||
|
||||
Each integration shows its current status:
|
||||
|
||||
* **Not connected**. integration has not been set up.
|
||||
* **Connected**. integration is active and being synced.
|
||||
* **Manage**. active integration with options to reconfigure or disconnect.
|
||||
|
||||
You can revoke any connection at any time from the Skills tab.
|
||||
|
||||
## Messaging channels
|
||||
|
||||
Three integrations are special. OpenHuman uses them to *talk back* to you, not just read from them:
|
||||
|
||||
* **Telegram**. the primary messaging channel. Two-way: send and receive messages, manage chats, search history, create groups, 80+ actions on your behalf. All actions run through your own encrypted credentials.
|
||||
* **Discord**. send and receive messages via Discord. Connect your account to receive OpenHuman messages there.
|
||||
* **Web**. a browser-based chat interface within the desktop app. Messages stay entirely local.
|
||||
|
||||
Set your default under **Settings → Automation & Channels → Messaging Channels**. The active route status shows which channel is currently in use. Telegram offers two credential modes: connect via OpenHuman (one-click, encrypted) or provide your own credentials for maximum control.
|
||||
|
||||
## Skills
|
||||
|
||||
Beyond third-party services, OpenHuman has **skills**, small sandboxed modules that run inside the app, fetch external data, run on a schedule, transform information, and respond to events. Each runs with enforced resource limits. Skills install from the Skills tab and integrate with the same Memory Tree as everything else.
|
||||
|
||||
## Native voice and tools
|
||||
|
||||
Two capabilities ship native rather than as integrations because they're load-bearing for the desktop experience:
|
||||
|
||||
* [**Voice**](voice.md). STT in, ElevenLabs TTS out, plus a live Google Meet agent that joins meetings, transcribes them into your Memory Tree, and can speak back into the call.
|
||||
* [**Native tools**](native-tools.md). built-in web search, web-fetch scraper, and a full filesystem/git/lint/test/grep coder toolset that the agent uses out of the box.
|
||||
|
||||
## Privacy boundary
|
||||
|
||||
OpenHuman's core never calls any third-party API directly. All requests go through the OpenHuman backend, which handles OAuth tokens and rate limiting. Your tokens never sit on disk in plaintext on your machine, and the agent only sees the _results_ of tool calls — not the credentials.
|
||||
OpenHuman's core never calls any third-party API directly. All requests go through the OpenHuman backend, which handles OAuth tokens and rate limiting. Your tokens never sit on disk in plaintext on your machine, and the agent only sees the _results_ of tool calls, not the credentials.
|
||||
|
||||
See [Privacy & Security](privacy-and-security.md) for the full boundary.
|
||||
|
||||
## See also
|
||||
|
||||
* [Auto-fetch from Integrations](auto-fetch.md)
|
||||
* [Memory Tree](obsidian-wiki/memory-tree.md)
|
||||
* [Skills & Integrations](../product/skills-and-integrations.md) — the product-level view.
|
||||
|
||||
@@ -9,7 +9,7 @@ icon: microchip
|
||||
|
||||
OpenHuman can run a local model on your machine for the workloads where keeping data on-device matters most: **memory embeddings, summary-tree building, and background reasoning loops**. It is **opt-in** and ships **off** by default.
|
||||
|
||||
This is a deliberate scoping. The previous design tried to put chat, vision, STT and TTS all on-device with Gemma 3 — and the result was a heavy, hardware-sensitive footprint that fought with what the rest of the product needed to be. Today, the things that benefit most from being local (recurring, low-latency, privacy-sensitive memory work) run local; the things that benefit most from frontier models (chat, reasoning, vision) stay cloud.
|
||||
This is a deliberate scoping. The previous design tried to put chat, vision, STT and TTS all on-device with Gemma 3, and the result was a heavy, hardware-sensitive footprint that fought with what the rest of the product needed to be. Today, the things that benefit most from being local (recurring, low-latency, privacy-sensitive memory work) run local; the things that benefit most from frontier models (chat, reasoning, vision) stay cloud.
|
||||
|
||||
## What runs local when you turn it on
|
||||
|
||||
@@ -21,7 +21,7 @@ This is a deliberate scoping. The previous design tried to put chat, vision, STT
|
||||
| **Learning / reflection** | small chat model | `src/openhuman/learning/reflection.rs` — passes that consolidate what was learned. |
|
||||
| **Subconscious** | small chat model | `src/openhuman/subconscious/executor.rs` — background evaluation loop. |
|
||||
|
||||
Each of these is a **per-feature opt-in flag**. Turning on local AI does not silently route everything through it — you choose the workloads.
|
||||
Each of these is a **per-feature opt-in flag**. Turning on local AI does not silently route everything through it, you choose the workloads.
|
||||
|
||||
## What stays in the cloud
|
||||
|
||||
@@ -40,7 +40,7 @@ For **lightweight or medium chat hints** (`hint:reaction`, `hint:classify`, `hin
|
||||
Under the hood, OpenHuman uses [Ollama](https://ollama.com) and talks to it over Ollama's OpenAI-compatible `/v1` endpoint. That means:
|
||||
|
||||
* The `OpenAiCompatibleProvider` (`src/openhuman/providers/compatible.rs`) wraps Ollama exactly the way it wraps a remote OpenAI-style provider. No special-case code path.
|
||||
* The provider router creates a _health-gated_ local provider on startup. If Ollama is not reachable, requests transparently fall back to the remote provider — no broken state.
|
||||
* The provider router creates a _health-gated_ local provider on startup. If Ollama is not reachable, requests transparently fall back to the remote provider, no broken state.
|
||||
* Models are pulled on demand by Ollama and cached in its own store. OpenHuman doesn't ship the weights itself.
|
||||
|
||||
## Opting in
|
||||
@@ -56,7 +56,7 @@ Local AI is gated by two flags in the core config (`src/openhuman/config/schema/
|
||||
| `local_ai.usage.learning_reflection` | `false` | Use local for learning passes. |
|
||||
| `local_ai.usage.subconscious` | `false` | Use local for the subconscious loop. |
|
||||
|
||||
In the desktop app, **Settings → AI & Skills → Local AI** exposes presets — pick one ("embeddings only", "memory + reflection", "everything local") and the right combination of flags is set for you. Status (Ollama reachability, model availability, per-subsystem enablement) is surfaced live via `openhuman.local_ai_status`.
|
||||
In the desktop app, **Settings → AI & Skills → Local AI** exposes presets, pick one ("embeddings only", "memory + reflection", "everything local") and the right combination of flags is set for you. Status (Ollama reachability, model availability, per-subsystem enablement) is surfaced live via `openhuman.local_ai_status`.
|
||||
|
||||
## When to turn it on
|
||||
|
||||
@@ -66,7 +66,7 @@ Local AI is worth turning on if any of these are true:
|
||||
* You want **summary-tree building** to work offline.
|
||||
* You're privacy-sensitive about background reflection ("subconscious") loops.
|
||||
|
||||
It is **not** worth turning on if you only have a few sources connected — the cloud path is faster and the privacy benefit is small. There is also a hardware cost: Ollama and a small Gemma model want a few GB of RAM and pull a few GB of weights.
|
||||
It is **not** worth turning on if you only have a few sources connected, the cloud path is faster and the privacy benefit is small. There is also a hardware cost: Ollama and a small Gemma model want a few GB of RAM and pull a few GB of weights.
|
||||
|
||||
## What you'll need
|
||||
|
||||
@@ -78,6 +78,6 @@ OpenHuman handles the rest: lifecycle (`src/openhuman/local_ai/service.rs`), API
|
||||
|
||||
## See also
|
||||
|
||||
* [Memory Tree](obsidian-wiki/memory-tree.md) — what local embeddings + summarization power.
|
||||
* [Automatic Model Routing](model-routing.md) — how lightweight chat hints prefer the local provider.
|
||||
* [Privacy & Security](../product/privacy-and-security.md) — what moves on-device when you opt in.
|
||||
* [Memory Tree](obsidian-wiki/memory-tree.md). what local embeddings + summarization power.
|
||||
* [Automatic Model Routing](model-routing.md). how lightweight chat hints prefer the local provider.
|
||||
* [Privacy & Security](../features/privacy-and-security.md). what moves on-device when you opt in.
|
||||
|
||||
@@ -13,8 +13,8 @@ Different parts of an agent want different models. Long reasoning wants a fronti
|
||||
|
||||
The model parameter on any chat call can take one of two shapes:
|
||||
|
||||
- **Concrete model name** — e.g. `anthropic/claude-sonnet-4`. Routes to the default provider with that exact model.
|
||||
- **Hint prefix** — e.g. `hint:reasoning`. Looks the hint up in the route table and resolves to a `(provider, model)` pair.
|
||||
- **Concrete model name**. e.g. `anthropic/claude-sonnet-4`. Routes to the default provider with that exact model.
|
||||
- **Hint prefix**. e.g. `hint:reasoning`. Looks the hint up in the route table and resolves to a `(provider, model)` pair.
|
||||
|
||||
```rust
|
||||
// src/openhuman/providers/router.rs
|
||||
@@ -44,13 +44,13 @@ The exact mappings are configurable; the defaults ship sensible per-provider rou
|
||||
|
||||
## One subscription
|
||||
|
||||
Routing happens behind a single OpenHuman subscription. You don't hold separate API keys for Anthropic, OpenAI, Google etc. — the backend brokers access, and the router picks the right one per task. That's the "one subscription, many providers" promise from the README, made concrete.
|
||||
Routing happens behind a single OpenHuman subscription. You don't hold separate API keys for Anthropic, OpenAI, Google etc., the backend brokers access, and the router picks the right one per task. That's the "one subscription, many providers" promise from the README, made concrete.
|
||||
|
||||
## Overriding routes
|
||||
|
||||
- **Globally** — config TOML (`Config` struct in `src/openhuman/config/schema/types.rs`) can supply a custom route table at startup.
|
||||
- **Per call** — pass a concrete model name (no `hint:` prefix) and the router falls through to the default provider with that exact model.
|
||||
- **For a skill** — skills can pin a hint or a model in their manifest.
|
||||
- **Globally**. config TOML (`Config` struct in `src/openhuman/config/schema/types.rs`) can supply a custom route table at startup.
|
||||
- **Per call**. pass a concrete model name (no `hint:` prefix) and the router falls through to the default provider with that exact model.
|
||||
- **For a skill**. skills can pin a hint or a model in their manifest.
|
||||
|
||||
## Why this isn't just "model switcher"
|
||||
|
||||
@@ -58,5 +58,5 @@ Routing isn't a UI dropdown. The agent loop itself emits hints based on what it'
|
||||
|
||||
## See also
|
||||
|
||||
- [Smart Token Compression](token-compression.md) — what makes large reasoning calls affordable.
|
||||
- [Native Tools](native-tools.md) — different tool calls hint at different routes.
|
||||
- [Smart Token Compression](token-compression.md). what makes large reasoning calls affordable.
|
||||
- [Native Tools](native-tools.md). different tool calls hint at different routes.
|
||||
|
||||
@@ -51,7 +51,7 @@ Together this is what makes OpenHuman a viable coding partner instead of a chat
|
||||
|
||||
## Why ship them natively
|
||||
|
||||
A plugin-only model means tools live in different processes, behind RPC, with their own auth and packaging stories. That's fine for open-ended extensibility — but for the **core** tools every agent needs (read a file, search the web, edit code), shipping them in-process means:
|
||||
A plugin-only model means tools live in different processes, behind RPC, with their own auth and packaging stories. That's fine for open-ended extensibility, but for the **core** tools every agent needs (read a file, search the web, edit code), shipping them in-process means:
|
||||
|
||||
- Consistent error handling.
|
||||
- Zero install friction.
|
||||
@@ -60,5 +60,5 @@ A plugin-only model means tools live in different processes, behind RPC, with th
|
||||
|
||||
## See also
|
||||
|
||||
- [Smart Token Compression](token-compression.md) — what keeps tool output costs bounded.
|
||||
- [Third-party Integrations](integrations.md) — for the long tail of third-party services.
|
||||
- [Smart Token Compression](token-compression.md). what keeps tool output costs bounded.
|
||||
- [Third-party Integrations](integrations.md). for the long tail of third-party services.
|
||||
|
||||
@@ -9,7 +9,7 @@ icon: book-open
|
||||
|
||||
<figure><img src="../../.gitbook/assets/image.png" alt=""><figcaption><p>A preview of the OpenHuman memory in Obsidian. Data from various sources (GMail, Slack, Whatsapp etc..) is organized as a memory tree.</p></figcaption></figure>
|
||||
|
||||
OpenHuman's memory is not a black box. The same chunks the agent reasons over are written as plain `.md` files into a vault inside your workspace. You can open it in [Obsidian](https://obsidian.md), browse it, edit it, and link notes by hand — and the agent will see your edits.
|
||||
OpenHuman's memory is not a black box. The same chunks the agent reasons over are written as plain `.md` files into a vault inside your workspace. You can open it in [Obsidian](https://obsidian.md), browse it, edit it, and link notes by hand, and the agent will see your edits.
|
||||
|
||||
The design is directly inspired by [Andrej Karpathy's obsidian-wiki workflow](https://x.com/karpathy/status/2039805659525644595): a personal wiki where every interesting thing in your life ends up as a linkable note.
|
||||
|
||||
@@ -23,13 +23,13 @@ The design is directly inspired by [Andrej Karpathy's obsidian-wiki workflow](ht
|
||||
└── … # one folder per connected toolkit you've connected
|
||||
```
|
||||
|
||||
The `summaries/` folder is laid out hierarchically — by date for the global tree, by source for source trees, by entity for topic trees. Each file's frontmatter carries provenance (source ids, time range, scope) so the agent can trace any claim back to the chunks that produced it.
|
||||
The `summaries/` folder is laid out hierarchically, by date for the global tree, by source for source trees, by entity for topic trees. Each file's frontmatter carries provenance (source ids, time range, scope) so the agent can trace any claim back to the chunks that produced it.
|
||||
|
||||
## Open the vault
|
||||
|
||||
In the desktop app, the **Memory** tab has a **"View vault in Obsidian"** button. It uses an `obsidian://open?path=...` deep link, so you need Obsidian installed.
|
||||
|
||||
You can also open the folder in any editor — it's just Markdown. Links between files use standard `[[wiki-link]]` syntax, so Obsidian's graph view, backlinks, and tag explorer all work out of the box.
|
||||
You can also open the folder in any editor, it's just Markdown. Links between files use standard `[[wiki-link]]` syntax, so Obsidian's graph view, backlinks, and tag explorer all work out of the box.
|
||||
|
||||
## Editing notes by hand
|
||||
|
||||
@@ -38,16 +38,16 @@ Anything you put in `wiki/notes/` is fair game for ingest. The same pipeline tha
|
||||
This means you can:
|
||||
|
||||
* Drop a meeting note in `wiki/notes/2026-05-08-board-call.md` and the agent will know the context tomorrow.
|
||||
* Maintain a file per project, per person, per ticker — the topic tree treats your manual notes as just another source.
|
||||
* Maintain a file per project, per person, per ticker, the topic tree treats your manual notes as just another source.
|
||||
* Bulk-import an existing Obsidian vault: drop the `.md` files in and trigger ingest.
|
||||
|
||||
## Why this matters
|
||||
|
||||
You can't trust a memory you can't read. Most "AI memory" systems hide the state in opaque embeddings; OpenHuman's vault is the inverse — the agent's memory is **literally** a folder of Markdown you own. If the agent gets something wrong, you can find the file, fix it, and the next retrieval is correct.
|
||||
You can't trust a memory you can't read. Most "AI memory" systems hide the state in opaque embeddings; OpenHuman's vault is the inverse, the agent's memory is **literally** a folder of Markdown you own. If the agent gets something wrong, you can find the file, fix it, and the next retrieval is correct.
|
||||
|
||||
It's also the cleanest possible export: stop using OpenHuman tomorrow and you keep a fully-formed personal wiki.
|
||||
|
||||
## See also
|
||||
|
||||
* [Memory Tree](memory-tree.md) — the pipeline that produces the vault.
|
||||
* [Auto-fetch from Integrations](../auto-fetch.md) — how the vault grows on its own.
|
||||
* [Memory Tree](memory-tree.md). the pipeline that produces the vault.
|
||||
* [Auto-fetch from Integrations](../auto-fetch.md). how the vault grows on its own.
|
||||
|
||||
@@ -7,12 +7,12 @@ icon: diagram-project
|
||||
|
||||
# Memory Tree Pipeline
|
||||
|
||||
The user-facing pitch of the [Memory Tree](memory-tree.md) is simple: connect a source, the agent gets persistent memory of it. The pipeline that delivers on that pitch is **not** simple — it spans an HTTP-triggered ingest path, a job queue inside SQLite, a pool of background workers, three independent summary trees, and a daily UTC scheduler. This page walks through the whole thing.
|
||||
The user-facing pitch of the [Memory Tree](memory-tree.md) is simple: connect a source, the agent gets persistent memory of it. The pipeline that delivers on that pitch is **not** simple, it spans an HTTP-triggered ingest path, a job queue inside SQLite, a pool of background workers, three independent summary trees, and a daily UTC scheduler. This page walks through the whole thing.
|
||||
|
||||
The diagram below is the source of truth. Every box maps to code under `src/openhuman/memory/tree/`.
|
||||
|
||||
{% file src="../../.gitbook/assets/memory-tree-pipeline (1).excalidraw" %}
|
||||
Memory Tree Async Pipeline — leaf ingestion → jobs queue → workers → source / topic / global tree building.
|
||||
Memory Tree Async Pipeline, leaf ingestion → jobs queue → workers → source / topic / global tree building.
|
||||
{% endfile %}
|
||||
|
||||
## The six lanes
|
||||
@@ -73,7 +73,7 @@ Code: `src/openhuman/memory/tree/store.rs`, `jobs/queue.rs`.
|
||||
|
||||
Bootstrap: `jobs::start(workspace_dir)` is called once at process startup. It:
|
||||
|
||||
* Calls `recover_stale_locks()` — any job whose `locked_until_ms` is in the past becomes `pending` again. Crashes don't strand work.
|
||||
* Calls `recover_stale_locks()`, any job whose `locked_until_ms` is in the past becomes `pending` again. Crashes don't strand work.
|
||||
* Spawns **3 worker tasks** (configurable, but 3 is the default and what production runs).
|
||||
* Wires a `tokio::sync::Notify` so the ingest path can wake workers immediately, with a **5-second polling fallback** so a missed notify doesn't strand work.
|
||||
* Holds a shared `Semaphore(3)` for LLM-bound steps so concurrent embedding / summarization calls can't blow past the configured budget.
|
||||
@@ -95,11 +95,11 @@ Code: `src/openhuman/memory/tree/jobs/{worker.rs, handlers/}`.
|
||||
|
||||
Three independent trees are built from the same leaf stream.
|
||||
|
||||
**Source tree** — one per source. New leaves land in the L0 buffer. When the buffer fills (or `flush_stale` fires), `seal` writes an L1 summary; if the L1 buffer fills, the cascade continues up.
|
||||
**Source tree**, one per source. New leaves land in the L0 buffer. When the buffer fills (or `flush_stale` fires), `seal` writes an L1 summary; if the L1 buffer fills, the cascade continues up.
|
||||
|
||||
**Topic tree** — one per high-hotness entity. The `topic_route` handler runs a curator check (is this entity hot enough to deserve its own tree?) and, if it passes, calls `append_buffer` against the topic's tree.
|
||||
**Topic tree**, one per high-hotness entity. The `topic_route` handler runs a curator check (is this entity hot enough to deserve its own tree?) and, if it passes, calls `append_buffer` against the topic's tree.
|
||||
|
||||
**Global tree** — one tree, growing one node per UTC day. The `digest_daily` handler builds yesterday's daily node and `append_daily_and_cascade` walks it up the global hierarchy.
|
||||
**Global tree**, one tree, growing one node per UTC day. The `digest_daily` handler builds yesterday's daily node and `append_daily_and_cascade` walks it up the global hierarchy.
|
||||
|
||||
Code: `src/openhuman/memory/tree/{tree_source, tree_topic, tree_global}/`.
|
||||
|
||||
@@ -125,7 +125,7 @@ pending_extraction ──► admitted ──► buffered ──► sealed
|
||||
```
|
||||
|
||||
* `extract_chunk` decides `admitted` vs `dropped` based on the deep score.
|
||||
* `append_buffer` moves admitted leaves into a buffer — `buffered`.
|
||||
* `append_buffer` moves admitted leaves into a buffer, `buffered`.
|
||||
* `seal` writes the buffer's contents into a summary and marks each leaf `sealed`.
|
||||
* `dropped` leaves stop here. Their chunk row stays for provenance, but no buffer / summary references them.
|
||||
|
||||
@@ -135,7 +135,7 @@ This is why retrieval can show provenance without re-running the pipeline: the c
|
||||
|
||||
Three reasons:
|
||||
|
||||
1. **Crash safety.** A worker panic, a process kill, a power loss — none of them lose admitted-but-not-yet-sealed work. The job row is durable in SQLite; the next start picks it up.
|
||||
1. **Crash safety.** A worker panic, a process kill, a power loss, none of them lose admitted-but-not-yet-sealed work. The job row is durable in SQLite; the next start picks it up.
|
||||
2. **Retries with backoff.** `attempts` + `available_at_ms` + `last_error` give us per-job retry without ad-hoc retry loops in business logic.
|
||||
3. **One throttle for LLM cost.** All summarization paths share a single semaphore, so a burst of new sources can't accidentally fan out to 50 concurrent embeddings calls.
|
||||
|
||||
@@ -150,6 +150,5 @@ A few rules to keep the pipeline coherent:
|
||||
|
||||
## See also
|
||||
|
||||
* [Memory Tree (user-facing)](memory-tree.md) — the product surface this pipeline powers.
|
||||
* [Memory Context Window](/broken/pages/l8k1mXW6gnYt3tp2WIGd) — how much of the resulting state lands in each agent turn.
|
||||
* [Local AI](../local-ai.md) — opt-in path for running embeddings + summarization on-device.
|
||||
* [Memory Tree (user-facing)](memory-tree.md). the product surface this pipeline powers.
|
||||
* [Local AI](../local-ai.md). opt-in path for running embeddings + summarization on-device.
|
||||
|
||||
@@ -7,7 +7,7 @@ icon: tree
|
||||
|
||||
# Memory Tree
|
||||
|
||||
The Memory Tree is OpenHuman's knowledge base. It is not a vector database with a thin "memory" wrapper. It is a deterministic, bucket-sealed pipeline that turns the messy stream of your day — chats, emails, documents, integration sync results — into structured, queryable, summary-backed Markdown that lives on your machine.
|
||||
The Memory Tree is OpenHuman's knowledge base. It is not a vector database with a thin "memory" wrapper. It is a deterministic, bucket-sealed pipeline that turns the messy stream of your day, chats, emails, documents, integration sync results, into structured, queryable, summary-backed Markdown that lives on your machine.
|
||||
|
||||
## What it does
|
||||
|
||||
@@ -40,15 +40,15 @@ tree_source / tree_topic / tree_global ── per-scope summary trees
|
||||
retrieval ── search · drill_down · topic · global · fetch
|
||||
```
|
||||
|
||||
The hot path (`canonicalize → chunk → stage → fast-score → persist → enqueue extract jobs`) is fast. Heavy work — embeddings, entity extraction, sealing summary buckets, daily digests — runs in background workers out of the `jobs/` queue so the UI never blocks.
|
||||
The hot path (`canonicalize → chunk → stage → fast-score → persist → enqueue extract jobs`) is fast. Heavy work, embeddings, entity extraction, sealing summary buckets, daily digests, runs in background workers out of the `jobs/` queue so the UI never blocks.
|
||||
|
||||
Embeddings and summary-tree building can run **on-device via Ollama** if you turn on [Local AI](../local-ai.md); otherwise they go through the OpenHuman backend like any other model call.
|
||||
|
||||
## Three trees, three scopes
|
||||
|
||||
* **Source trees** (`tree_source/`) — per-source rolling buffer (L0) that seals into L1 → L2 → … as it fills. One per Gmail label, one per Slack channel, one per uploaded document, etc.
|
||||
* **Topic trees** (`tree_topic/`) — per-entity summaries materialized lazily by _hotness_. The more an entity (person, project, ticker, repo) shows up, the more aggressively its topic tree is built and refreshed.
|
||||
* **Global tree** (`tree_global/`) — daily global digest across everything ingested that day.
|
||||
* **Source trees** (`tree_source/`), per-source rolling buffer (L0) that seals into L1 → L2 → … as it fills. One per Gmail label, one per Slack channel, one per uploaded document, etc.
|
||||
* **Topic trees** (`tree_topic/`), per-entity summaries materialized lazily by _hotness_. The more an entity (person, project, ticker, repo) shows up, the more aggressively its topic tree is built and refreshed.
|
||||
* **Global tree** (`tree_global/`), daily global digest across everything ingested that day.
|
||||
|
||||
Retrieval can target any scope: search a single source, drill down a topic, or pull the global digest.
|
||||
|
||||
@@ -71,18 +71,44 @@ Vector stores answer "what is similar to this query?" Memory needs to answer mor
|
||||
* **What's the latest on this person?** (topic tree, hotness-driven)
|
||||
* **What did the Stripe webhook say last Tuesday at 3pm?** (source tree + provenance)
|
||||
|
||||
Trees give you compression _and_ navigation. Embeddings still live inside (in `score/`) so semantic search keeps working — but the structure on top is what makes the memory feel like a brain instead of a bag of fragments.
|
||||
Trees give you compression _and_ navigation. Embeddings still live inside (in `score/`) so semantic search keeps working, but the structure on top is what makes the memory feel like a brain instead of a bag of fragments.
|
||||
|
||||
## Triggering ingest
|
||||
|
||||
* **Automatic** — every active integration is auto-fetched every five minutes; see [Auto-fetch](../auto-fetch.md).
|
||||
* **Manual** — the Memory tab in the desktop app exposes a "Run ingest" trigger per source.
|
||||
* **RPC** — `openhuman.memory_tree_ingest` for advanced workflows.
|
||||
* **Automatic**. every active integration is auto-fetched every five minutes; see [Auto-fetch](../auto-fetch.md).
|
||||
* **Manual**. the Memory tab in the desktop app exposes a "Run ingest" trigger per source.
|
||||
* **RPC**. `openhuman.memory_tree_ingest` for advanced workflows.
|
||||
|
||||
## In the desktop app — the Intelligence tab
|
||||
|
||||
Open it from the bottom navigation bar.
|
||||
|
||||
**System status.** The top of the page shows the current state (idle, ingesting, summarizing) and a **Run ingest** button to manually trigger a sync against any connected source.
|
||||
|
||||
**Memory metrics:**
|
||||
|
||||
| Metric | What it shows |
|
||||
| --- | --- |
|
||||
| **Storage** | Total size of `<workspace>/memory_tree/chunks.db` and the Obsidian vault. |
|
||||
| **Sources** | How many distinct sources have been ingested (one per Gmail label, Slack channel, document, etc.). |
|
||||
| **Chunks** | Total ≤3k-token chunks in the store. |
|
||||
| **Topics** | Number of topic trees materialized so far (per-entity summaries built from "hot" entities). |
|
||||
| **First / latest memory** | Timestamps of the oldest and newest chunks. |
|
||||
|
||||
**Memory graph.** A force-directed visualization of entities and their relationships, drawn from the entity index. The graph grows as auto-fetch pulls more data, sparse early on, denser within a few days.
|
||||
|
||||
**Obsidian vault.** A **View vault in Obsidian** button opens `<workspace>/wiki/` directly via an `obsidian://open?path=...` deep link. You can also open the folder in any file browser.
|
||||
|
||||
**Ingestion activity.** A heatmap showing ingest events over time, similar to a GitHub contribution graph. Useful for spotting periods where auto-fetch was idle (e.g. a connection broke and stopped syncing).
|
||||
|
||||
**Search & retrieval.** A search bar over the Memory Tree. Source-scoped, topic-scoped or global queries are all supported, and any result links back to the underlying chunk file in your Obsidian vault for full provenance.
|
||||
|
||||
**Routing.** The Intelligence tab also surfaces which model the agent is using per task, see [Automatic Model Routing](../model-routing.md).
|
||||
|
||||
## See also
|
||||
|
||||
* [Obsidian Wiki](./) — open the vault in Obsidian and edit it directly.
|
||||
* [Auto-fetch from Integrations](../auto-fetch.md) — how the tree stays fresh.
|
||||
* [Smart Token Compression](../token-compression.md) — what makes ingesting "everything" cheap.
|
||||
* [Local AI (optional)](../local-ai.md) — opt in to keep embeddings and summary-tree building on-device.
|
||||
* [Memory Tree Pipeline](memory-tree-pipeline.md) — contributor-facing deep dive on the async queue, workers and tree-state machine.
|
||||
* [Obsidian Wiki](./). open the vault in Obsidian and edit it directly.
|
||||
* [Auto-fetch from Integrations](../auto-fetch.md). how the tree stays fresh.
|
||||
* [Smart Token Compression](../token-compression.md). what makes ingesting "everything" cheap.
|
||||
* [Local AI (optional)](../local-ai.md). opt in to keep embeddings and summary-tree building on-device.
|
||||
* [Memory Tree Pipeline](memory-tree-pipeline.md). contributor-facing deep dive on the async queue, workers and tree-state machine.
|
||||
|
||||
@@ -4,7 +4,7 @@ icon: layer-plus
|
||||
|
||||
# Platform & Availability
|
||||
|
||||
OpenHuman is a native desktop application — not a browser extension, not an Electron wrapper. Built on **React + Tauri v2** with a **Rust core**, it ships small, starts fast, and stays out of the way.
|
||||
OpenHuman is a native desktop application, not a browser extension, not an Electron wrapper. Built on **React + Tauri v2** with a **Rust core**, it ships small, starts fast, and stays out of the way.
|
||||
|
||||
***
|
||||
|
||||
@@ -16,8 +16,6 @@ OpenHuman is a native desktop application — not a browser extension, not an El
|
||||
| **Windows** | x64, ARM64 | `.msi` installer |
|
||||
| **Linux** | x64, ARM64 | AppImage, `.deb`, apt |
|
||||
|
||||
See [Install](/broken/pages/OyFFC8PaGMAWetIn8vKG) for setup on each platform.
|
||||
|
||||
***
|
||||
|
||||
## Why native matters
|
||||
@@ -28,7 +26,7 @@ OpenHuman is built as a native application rather than a web wrapper for three r
|
||||
|
||||
**Fast startup.** No browser engine to initialize. Ready to accept requests immediately.
|
||||
|
||||
**OS-level security.** Credentials live in your platform's secure keychain — macOS Keychain, Windows Credential Manager, Linux Secret Service. Sensitive data never sits in browser storage or plain text files. The local Memory Tree's SQLite database lives in your workspace folder, owned by you.
|
||||
**OS-level security.** Credentials live in your platform's secure keychain, macOS Keychain, Windows Credential Manager, Linux Secret Service. Sensitive data never sits in browser storage or plain text files. The local Memory Tree's SQLite database lives in your workspace folder, owned by you.
|
||||
|
||||
***
|
||||
|
||||
@@ -63,7 +61,7 @@ The desktop app maintains a persistent connection to the OpenHuman backend. Resp
|
||||
|
||||
## Offline behavior
|
||||
|
||||
Your local state persists on your device. Preferences, settings, and connected-source configurations remain available offline. The local Memory Tree is fully accessible — you can browse the [Obsidian vault](../features/obsidian-wiki/) and read your existing notes without any network connection.
|
||||
Your local state persists on your device. Preferences, settings, and connected-source configurations remain available offline. The local Memory Tree is fully accessible, you can browse the [Obsidian vault](../features/obsidian-wiki/) and read your existing notes without any network connection.
|
||||
|
||||
Auto-fetch and live LLM calls require connectivity. When the network returns, the next 5-minute tick picks up where it left off.
|
||||
|
||||
@@ -71,4 +69,4 @@ Auto-fetch and live LLM calls require connectivity. When the network returns, th
|
||||
|
||||
## Auto-update
|
||||
|
||||
The desktop shell auto-updates itself via Tauri's updater plugin against a manifest published on GitHub Releases. The OpenHuman core sidecar ships inside the same bundle, so a shell update upgrades both. See [Auto-update](/broken/pages/nD6VkS4K7taRc7cwDSus).
|
||||
The desktop shell auto-updates itself via Tauri's updater plugin against a manifest published on GitHub Releases. The OpenHuman core sidecar ships inside the same bundle, so a shell update upgrades both.
|
||||
+8
-8
@@ -4,7 +4,7 @@ icon: shield
|
||||
|
||||
# Privacy & Security
|
||||
|
||||
OpenHuman is designed so that the **memory of your life lives on your machine**. The local SQLite Memory Tree, the Markdown Obsidian vault, your audio buffers — all of that stays under your control. The OpenHuman backend handles things that have to be brokered (LLM calls, OAuth tokens, search proxying), and nothing more.
|
||||
OpenHuman is designed so that the **memory of your life lives on your machine**. The local SQLite Memory Tree, the Markdown Obsidian vault, your audio buffers, all of that stays under your control. The OpenHuman backend handles things that have to be brokered (LLM calls, OAuth tokens, search proxying), and nothing more.
|
||||
|
||||
***
|
||||
|
||||
@@ -12,9 +12,9 @@ OpenHuman is designed so that the **memory of your life lives on your machine**.
|
||||
|
||||
**The Memory Tree is local.** The SQLite database (`<workspace>/memory_tree/chunks.db`) and the Markdown vault (`<workspace>/wiki/`) live on your machine. The agent reads from them locally; nothing about your raw source data sits on the OpenHuman backend.
|
||||
|
||||
**Integration tokens are held by the backend, not on your laptop.** OAuth tokens are never written to disk in plaintext on your device. The OpenHuman backend brokers each integration request — the core never speaks any third-party API directly.
|
||||
**Integration tokens are held by the backend, not on your laptop.** OAuth tokens are never written to disk in plaintext on your device. The OpenHuman backend brokers each integration request, the core never speaks any third-party API directly.
|
||||
|
||||
**OS-level credential storage.** Sensitive tokens are stored in your platform's secure keychain — macOS Keychain, Windows Credential Manager, Linux Secret Service.
|
||||
**OS-level credential storage.** Sensitive tokens are stored in your platform's secure keychain, macOS Keychain, Windows Credential Manager, Linux Secret Service.
|
||||
|
||||
**No training on your data.** Your conversations, your Memory Tree, and your personal information are never used to train AI models or improve systems.
|
||||
|
||||
@@ -46,7 +46,7 @@ OpenHuman is designed so that the **memory of your life lives on your machine**.
|
||||
|
||||
OpenHuman accesses an integration only after you complete its OAuth flow. Each connection has its own scope; you can revoke any of them at any time from the Skills tab.
|
||||
|
||||
[Auto-fetch](../features/auto-fetch.md) does run continuously while a connection is active — that is the whole point. But it is bound by:
|
||||
[Auto-fetch](../features/auto-fetch.md) does run continuously while a connection is active, that is the whole point. But it is bound by:
|
||||
|
||||
- The **OAuth scope** you granted that integration.
|
||||
- A **per-provider sync interval** (e.g. Gmail every 15 min by default).
|
||||
@@ -60,11 +60,11 @@ If you revoke a connection, the next tick stops syncing it; chunks already in yo
|
||||
|
||||
Most AI assistants face a tradeoff: more context means more raw data sent to the cloud. The Memory Tree eliminates this tradeoff.
|
||||
|
||||
Because canonicalization, chunking, scoring and summary trees all run **inside your local Rust core**, your raw source data never leaves your machine. The only thing the LLM sees is what the agent retrieves from your local Memory Tree at the moment of a turn — and that retrieval is governed by your prompt, not by background uploads.
|
||||
Because canonicalization, chunking, scoring and summary trees all run **inside your local Rust core**, your raw source data never leaves your machine. The only thing the LLM sees is what the agent retrieves from your local Memory Tree at the moment of a turn, and that retrieval is governed by your prompt, not by background uploads.
|
||||
|
||||
Compression and locality together become the privacy architecture.
|
||||
|
||||
<figure><img src="../.gitbook/assets/V17 — Privacy Shield@2x.png" alt=""><figcaption></figcaption></figure>
|
||||
<figure><img src="../.gitbook/assets/V17. Privacy Shield@2x.png" alt=""><figcaption></figcaption></figure>
|
||||
|
||||
## Security
|
||||
|
||||
@@ -84,7 +84,7 @@ OpenHuman includes an intelligence layer designed to help you reason about credi
|
||||
|
||||
**Scam and impersonation signals.** Behavioral patterns associated with scams, impersonation, or coordinated abuse can surface as warnings. Signals come from patterns, not from sharing individual message content.
|
||||
|
||||
**Contextual dynamic trust.** Trust is contextual — credibility in one domain does not automatically transfer to another. OpenHuman represents trust through aggregated artifacts and historical accuracy rather than static scores.
|
||||
**Contextual dynamic trust.** Trust is contextual, credibility in one domain does not automatically transfer to another. OpenHuman represents trust through aggregated artifacts and historical accuracy rather than static scores.
|
||||
|
||||
**Advisory, not enforcement.** Trust and risk outputs are advisory signals to inform your judgment. OpenHuman does not ban users, remove messages, or enforce moderation decisions.
|
||||
|
||||
@@ -94,4 +94,4 @@ OpenHuman includes an intelligence layer designed to help you reason about credi
|
||||
|
||||
In team or community settings, privacy remains user-centric. Each user's connected sources are scoped to their account; admins do not get a backdoor into other users' Memory Trees.
|
||||
|
||||
Community-level intelligence is derived from aggregated and anonymized signals — never from direct access to individual message content.
|
||||
Community-level intelligence is derived from aggregated and anonymized signals, never from direct access to individual message content.
|
||||
@@ -92,7 +92,7 @@ Query: enabled, not completed, `next_run_at <= now` or never run.
|
||||
|
||||
### 3. Log as in_progress
|
||||
|
||||
Each due task gets a single log row inserted with `decision = "in_progress"`. This row is updated in place as the task progresses — no duplicate rows.
|
||||
Each due task gets a single log row inserted with `decision = "in_progress"`. This row is updated in place as the task progresses, no duplicate rows.
|
||||
|
||||
### 4. Evaluate with local model
|
||||
|
||||
@@ -197,11 +197,11 @@ Escalations related to skills (detected by keywords: skill, oauth, notion, gmail
|
||||
|
||||
Tracked in `EngineState.consecutive_failures`. Increments when the entire LLM evaluation fails (Ollama down, network error). Resets to 0 on any successful tick. Surfaced in the UI status bar as "N failed" in coral.
|
||||
|
||||
Individual task execution failures do NOT increment this counter — they are logged per-task but the tick itself is considered successful.
|
||||
Individual task execution failures do NOT increment this counter, they are logged per-task but the tick itself is considered successful.
|
||||
|
||||
### last_tick_at advancement
|
||||
|
||||
`last_tick_at` only advances on successful ticks. If the LLM evaluation fails or the tick is cancelled, `last_tick_at` stays unchanged so the next tick's situation report covers the same time range — nothing is missed.
|
||||
`last_tick_at` only advances on successful ticks. If the LLM evaluation fails or the tick is cancelled, `last_tick_at` stays unchanged so the next tick's situation report covers the same time range, nothing is missed.
|
||||
|
||||
---
|
||||
|
||||
@@ -311,4 +311,4 @@ Chronological list of task evaluations. Each entry shows timestamp, colored dot,
|
||||
|
||||
### Run Now
|
||||
|
||||
Triggers a manual tick. The tick runs in the background — the RPC returns immediately and the UI polls for updates.
|
||||
Triggers a manual tick. The tick runs in the background, the RPC returns immediately and the UI polls for updates.
|
||||
@@ -7,15 +7,15 @@ icon: file-zipper
|
||||
|
||||
# Smart Token Compression
|
||||
|
||||
LLM tokens are expensive — and verbose tool output is where most of them go to die. A `git status` in a busy repo, a `cargo build` log, a 600-message email thread, a `docker ps -a` against a real cluster — each of these can balloon a context window for almost no information gain.
|
||||
LLM tokens are expensive, and verbose tool output is where most of them go to die. A `git status` in a busy repo, a `cargo build` log, a 600-message email thread, a `docker ps -a` against a real cluster, each of these can balloon a context window for almost no information gain.
|
||||
|
||||
OpenHuman ships with **TokenJuice**, a port of [vincentkoc/tokenjuice](https://github.com/vincentkoc/tokenjuice) integrated directly into the tool-execution path. Before any tool result reaches the model, TokenJuice runs the output through a rule overlay that strips the noise and keeps the signal.
|
||||
|
||||
## Three-layer rule overlay
|
||||
|
||||
Rules are JSON, and they merge in this order — later layers override earlier ones:
|
||||
Rules are JSON, and they merge in this order, later layers override earlier ones:
|
||||
|
||||
<table><thead><tr><th width="134.41796875">Layer</th><th>Path</th><th>Purpose</th></tr></thead><tbody><tr><td><strong>Builtin</strong></td><td>shipped with the binary</td><td>sensible defaults for git, npm, cargo, docker, kubectl, ls, etc.</td></tr><tr><td><strong>User</strong></td><td><code>~/.config/tokenjuice/rules/</code></td><td>your personal overrides — apply across every project</td></tr><tr><td><strong>Project</strong></td><td><code>.tokenjuice/rules/</code></td><td>repo-specific overrides — checked in, shared with the team</td></tr></tbody></table>
|
||||
<table><thead><tr><th width="134.41796875">Layer</th><th>Path</th><th>Purpose</th></tr></thead><tbody><tr><td><strong>Builtin</strong></td><td>shipped with the binary</td><td>sensible defaults for git, npm, cargo, docker, kubectl, ls, etc.</td></tr><tr><td><strong>User</strong></td><td><code>~/.config/tokenjuice/rules/</code></td><td>your personal overrides, apply across every project</td></tr><tr><td><strong>Project</strong></td><td><code>.tokenjuice/rules/</code></td><td>repo-specific overrides, checked in, shared with the team</td></tr></tbody></table>
|
||||
|
||||
Each rule names a tool/command pattern and a reduction strategy (truncate, dedup lines, fold whitespace, drop matching regexes, summarize sections, …). New rules are just JSON files; no recompile required.
|
||||
|
||||
@@ -47,5 +47,5 @@ Implementation: `src/openhuman/tokenjuice/` (`classify.rs`, `reduce.rs`, `rules/
|
||||
|
||||
## See also
|
||||
|
||||
* [Native Tools](native-tools.md) — most heavy tool output flows through TokenJuice.
|
||||
* [Memory Tree](obsidian-wiki/memory-tree.md) — the downstream consumer of compressed output.
|
||||
* [Native Tools](native-tools.md). most heavy tool output flows through TokenJuice.
|
||||
* [Memory Tree](obsidian-wiki/memory-tree.md). the downstream consumer of compressed output.
|
||||
|
||||
@@ -13,11 +13,11 @@ OpenHuman is voice-first when you want it to be. STT, TTS and the live Google Me
|
||||
|
||||
Voice input runs through `src/openhuman/voice/`:
|
||||
|
||||
* **Hotkey** (`hotkey.rs`) — push-to-talk and toggle modes.
|
||||
* **Audio capture** (`audio_capture.rs`) — cross-platform mic capture with VAD.
|
||||
* **Streaming transcription** (`streaming.rs`, `cloud_transcribe.rs`) — words appear as you speak.
|
||||
* **Hallucination filter** (`hallucination.rs`) — strips the well-known artefacts ("Thanks for watching", silence-induced phrases).
|
||||
* **Postprocess** (`postprocess.rs`) — punctuation, capitalization, dictation cleanup.
|
||||
* **Hotkey** (`hotkey.rs`), push-to-talk and toggle modes.
|
||||
* **Audio capture** (`audio_capture.rs`), cross-platform mic capture with VAD.
|
||||
* **Streaming transcription** (`streaming.rs`, `cloud_transcribe.rs`), words appear as you speak.
|
||||
* **Hallucination filter** (`hallucination.rs`), strips the well-known artefacts ("Thanks for watching", silence-induced phrases).
|
||||
* **Postprocess** (`postprocess.rs`), punctuation, capitalization, dictation cleanup.
|
||||
|
||||
Dictation can replace the active text input on your desktop, or be sent straight into a chat with the agent.
|
||||
|
||||
@@ -37,17 +37,17 @@ The Meet agent (`src/openhuman/meet_agent/brain.rs`) is OpenHuman's flagship voi
|
||||
|
||||
* Joins a Google Meet via the embedded webview.
|
||||
* Streams audio out to STT in real time, transcribes everyone in the call, and writes structured notes into the [Memory Tree](obsidian-wiki/memory-tree.md) as the meeting progresses.
|
||||
* When you ask it to speak (or it decides it has something useful to add), it generates audio with ElevenLabs and **plays it back into the meeting as an outbound camera/mic stream** — so other participants actually hear it.
|
||||
* When you ask it to speak (or it decides it has something useful to add), it generates audio with ElevenLabs and **plays it back into the meeting as an outbound camera/mic stream**, so other participants actually hear it.
|
||||
|
||||
This is real, not a demo: see commits `0bc74575` (live note-taking), `f1203479` (real LLM turns + tuned TTS), `b6d05cb4` (mascot canvas as outbound camera).
|
||||
|
||||
## Privacy
|
||||
|
||||
* Audio capture is local. Streaming STT goes through the OpenHuman backend; no recording is retained beyond the live transcript.
|
||||
* TTS audio is streamed and discarded — nothing is stored.
|
||||
* TTS audio is streamed and discarded, nothing is stored.
|
||||
* Meeting transcripts land in your local memory tree, like any other source.
|
||||
|
||||
## See also
|
||||
|
||||
* [Memory Tree](obsidian-wiki/memory-tree.md) — where Meet transcripts and notes live.
|
||||
* [Automatic Model Routing](model-routing.md) — Meet's brain uses `hint:fast` for low-latency conversational turns.
|
||||
* [Memory Tree](obsidian-wiki/memory-tree.md). where Meet transcripts and notes live.
|
||||
* [Automatic Model Routing](model-routing.md). Meet's brain uses `hint:fast` for low-latency conversational turns.
|
||||
|
||||
@@ -4,7 +4,7 @@ icon: play
|
||||
|
||||
# Getting Started
|
||||
|
||||
This page walks you through setting up OpenHuman and running your first request. For OS-by-OS install commands, see [Install](/broken/pages/OyFFC8PaGMAWetIn8vKG).
|
||||
This page walks you through setting up OpenHuman and running your first request.
|
||||
|
||||
OpenHuman is open source under the GNU GPL3 license. The codebase is at [github.com/tinyhumansai/openhuman](https://github.com/tinyhumansai/openhuman).
|
||||
|
||||
@@ -22,7 +22,7 @@ The first time you launch OpenHuman, the OS will prompt for the permissions the
|
||||
|
||||
## Download & install
|
||||
|
||||
Get the OpenHuman desktop app from [openhuman.ai](https://openhuman.ai) or via your platform's package manager. See [Install](/broken/pages/OyFFC8PaGMAWetIn8vKG) for the full matrix (Homebrew, apt, npm, curl, .dmg, .msi, AppImage).
|
||||
Get the OpenHuman desktop app from [openhuman.ai](https://openhuman.ai) or via your platform's package manager.
|
||||
|
||||
***
|
||||
|
||||
@@ -44,10 +44,10 @@ The 118+ catalog spans Gmail, Notion, GitHub, Slack, Stripe, Calendar, Drive, Li
|
||||
|
||||
Recommended starting points:
|
||||
|
||||
* **Gmail** — high-signal, has a native ingest path into the Memory Tree.
|
||||
* **Slack** — picks up workplace chat context fast.
|
||||
* **Notion** — for structured docs and exports.
|
||||
* **GitHub** — if you write code.
|
||||
* **Gmail**. high-signal, has a native ingest path into the Memory Tree.
|
||||
* **Slack**. picks up workplace chat context fast.
|
||||
* **Notion**. for structured docs and exports.
|
||||
* **GitHub**. if you write code.
|
||||
|
||||
Click **Connect** on any integration, complete the OAuth flow, and the next [auto-fetch](../features/auto-fetch.md) tick will start syncing it within five minutes.
|
||||
|
||||
@@ -73,13 +73,13 @@ Once a source is connected and auto-fetch has run a tick, try prompts like:
|
||||
* "Connect what my team discussed in Slack with what I was reviewing in Notion."
|
||||
* "What did Sarah say about the project across email and chat?"
|
||||
|
||||
OpenHuman picks the right model for each task automatically — see [Automatic Model Routing](../features/model-routing.md).
|
||||
OpenHuman picks the right model for each task automatically, see [Automatic Model Routing](../features/model-routing.md).
|
||||
|
||||
***
|
||||
|
||||
## Open the Obsidian vault
|
||||
|
||||
The Memory tab has a **View vault in Obsidian** button. Click it to open `<workspace>/wiki/` in [Obsidian](https://obsidian.md). You can browse summaries, drop in your own notes, and even build manual links — the agent will see your edits next ingest. See [Obsidian Wiki](../features/obsidian-wiki/).
|
||||
The Memory tab has a **View vault in Obsidian** button. Click it to open `<workspace>/wiki/` in [Obsidian](https://obsidian.md). You can browse summaries, drop in your own notes, and even build manual links, the agent will see your edits next ingest. See [Obsidian Wiki](../features/obsidian-wiki/).
|
||||
|
||||
***
|
||||
|
||||
@@ -87,10 +87,10 @@ The Memory tab has a **View vault in Obsidian** button. Click it to open `<works
|
||||
|
||||
After your first request, explore what else OpenHuman can do:
|
||||
|
||||
* **Skills** extend the assistant's capabilities — fetching data, running scheduled tasks, processing information.
|
||||
* **Skills** extend the assistant's capabilities, fetching data, running scheduled tasks, processing information.
|
||||
* **Integrations** let you push structured results to Notion, Google Sheets, and other connected tools.
|
||||
|
||||
Learn more in [Skills & Integrations](../product/skills-and-integrations.md).
|
||||
Learn more in [Skills & Integrations](../features/integrations.md).
|
||||
|
||||
***
|
||||
|
||||
|
||||
@@ -20,13 +20,13 @@ Three trees layer on top: per-source, per-topic (entities), and a daily global d
|
||||
|
||||
### 2. An [Obsidian-style wiki](../features/obsidian-wiki/) on top of that knowledge base
|
||||
|
||||
The same chunks the agent reasons over are written as `.md` files in `<workspace>/wiki/`. You can open the vault in [Obsidian](https://obsidian.md), browse it, edit it, drop in your own notes — and the agent will see your edits next ingest. This is directly inspired by Karpathy's obsidian-wiki workflow.
|
||||
The same chunks the agent reasons over are written as `.md` files in `<workspace>/wiki/`. You can open the vault in [Obsidian](https://obsidian.md), browse it, edit it, drop in your own notes, and the agent will see your edits next ingest. This is directly inspired by Karpathy's obsidian-wiki workflow.
|
||||
|
||||
You can't trust a memory you can't read. The vault is the inverse of the usual "AI memory" black box.
|
||||
|
||||
### 3. [third-party integrations](../features/integrations.md) feeding the tree on autopilot
|
||||
|
||||
OpenHuman ships with **118+ third-party integrations** — Gmail, Notion, GitHub, Slack, Stripe, Calendar, Drive, Linear, Jira, and more. Connecting any of them is a one-click OAuth flow.
|
||||
OpenHuman ships with **118+ third-party integrations**. Gmail, Notion, GitHub, Slack, Stripe, Calendar, Drive, Linear, Jira, and more. Connecting any of them is a one-click OAuth flow.
|
||||
|
||||
Once connected, the [auto-fetch scheduler](../features/auto-fetch.md) ticks every five minutes, pulls fresh data from every active connection, and pipes the results through the same ingest path the manual UI uses. By the time you ask "what landed in my inbox overnight?", the answer is already in the memory tree.
|
||||
|
||||
@@ -34,10 +34,10 @@ Once connected, the [auto-fetch scheduler](../features/auto-fetch.md) ticks ever
|
||||
|
||||
When you talk to the agent, four things happen behind the scenes:
|
||||
|
||||
* **Model routing** — The model parameter can be a hint (`hint:reasoning`, `hint:fast`, `hint:vision`). The [router](../features/model-routing.md) resolves the hint to the right provider+model. One subscription, many models.
|
||||
* **Native tools** — A built-in [web search, web-fetch scraper, and full filesystem/git/lint/test/grep coder toolset](../features/native-tools.md) are wired in by default. No "install a plugin to read files" friction.
|
||||
* **TokenJuice compression** — Verbose tool output (git logs, large emails, build output) is compacted by a [rule overlay](../features/token-compression.md) before it ever enters the model's context. Sweeping through your last six months of email costs single-digit dollars instead of hundreds.
|
||||
* **Voice, when you want it** — STT in, [ElevenLabs TTS](../features/voice.md) out, with a live Google Meet agent that can listen, take notes, and speak back into the call.
|
||||
* **Model routing**. The model parameter can be a hint (`hint:reasoning`, `hint:fast`, `hint:vision`). The [router](../features/model-routing.md) resolves the hint to the right provider+model. One subscription, many models.
|
||||
* **Native tools**. A built-in [web search, web-fetch scraper, and full filesystem/git/lint/test/grep coder toolset](../features/native-tools.md) are wired in by default. No "install a plugin to read files" friction.
|
||||
* **TokenJuice compression**. Verbose tool output (git logs, large emails, build output) is compacted by a [rule overlay](../features/token-compression.md) before it ever enters the model's context. Sweeping through your last six months of email costs single-digit dollars instead of hundreds.
|
||||
* **Voice, when you want it**. STT in, [ElevenLabs TTS](../features/voice.md) out, with a live Google Meet agent that can listen, take notes, and speak back into the call.
|
||||
|
||||
## How they connect
|
||||
|
||||
@@ -72,8 +72,8 @@ When you talk to the agent, four things happen behind the scenes:
|
||||
* Audio capture and dictation buffers.
|
||||
* Any local model state.
|
||||
|
||||
What goes through the OpenHuman backend: model calls (under one subscription), web search proxy, integration OAuth tokens, TTS streaming. See [Privacy & Security](../product/privacy-and-security.md) for the full boundary.
|
||||
What goes through the OpenHuman backend: model calls (under one subscription), web search proxy, integration OAuth tokens, TTS streaming. See [Privacy & Security](../features/privacy-and-security.md) for the full boundary.
|
||||
|
||||
## Limitations
|
||||
|
||||
OpenHuman runs on probabilistic models. It can miss nuance, mishandle sarcasm, or weight things wrong — especially in noisy informal threads with limited prior context. Auto-fetch is bound by the rate limits of each integration, so very high-volume sources may lag the global tick by a few minutes. The product is in early beta; expect rough edges and breaking changes.
|
||||
OpenHuman runs on probabilistic models. It can miss nuance, mishandle sarcasm, or weight things wrong, especially in noisy informal threads with limited prior context. Auto-fetch is bound by the rate limits of each integration, so very high-volume sources may lag the global tick by a few minutes. The product is in early beta; expect rough edges and breaking changes.
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
---
|
||||
icon: layer-plus
|
||||
---
|
||||
|
||||
# Platform & Availability
|
||||
|
||||
OpenHuman is a native **desktop** application. It is not a web-only tool, browser extension, or Electron wrapper. It is built for performance, security, and a small footprint on the machines where we officially support it today.
|
||||
|
||||
---
|
||||
|
||||
## Supported today: desktop
|
||||
|
||||
OpenHuman ships native installers for:
|
||||
|
||||
| Platform | Architectures | Distribution |
|
||||
| ----------- | -------------------- | -------------- |
|
||||
| **macOS** | Intel, Apple Silicon | .dmg installer |
|
||||
| **Windows** | x64, ARM64 | .msi installer |
|
||||
| **Linux** | x64, ARM64 | AppImage, .deb |
|
||||
|
||||
**Android, iOS, and a standalone web client are not supported** in documentation or releases yet. The codebase may contain experimental mobile or web targets; they are out of scope for current user-facing docs.
|
||||
|
||||
---
|
||||
|
||||
## Why Native Matters
|
||||
|
||||
OpenHuman is built as a native application rather than a web wrapper for three reasons.
|
||||
|
||||
**Small footprint.** The app is lightweight. A fraction of the size of typical communication tools. It starts in under a second and uses minimal memory, so it stays out of the way when running alongside other demanding applications.
|
||||
|
||||
**Fast startup.** There is no browser engine to initialize. The app launches quickly and is ready to accept requests immediately.
|
||||
|
||||
**OS-level security.** On desktop platforms, OpenHuman stores credentials in your operating system's secure keychain (macOS Keychain, Windows Credential Manager, Linux Secret Service). Sensitive data never sits in browser storage or plain text files.
|
||||
|
||||
---
|
||||
|
||||
## Architecture at a Glance
|
||||
|
||||
OpenHuman operates across three layers:
|
||||
|
||||
**Application layer.** The native app on your device handles the interface, user input, local state, credential management, and skill execution. This layer is responsible for everything you see and interact with.
|
||||
|
||||
**Intelligence layer.** OpenHuman's analysis, coordination, and intelligence systems run as a secure backend service. When a request requires deeper language processing, it is handled here. This layer is operated and maintained by OpenHuman.
|
||||
|
||||
**External services.** Connected tools and platforms: Telegram, Notion, Google Sheets, and others are accessed only when you explicitly request it. OpenHuman acts as a bridge between your sources and the intelligence layer, not as a replacement for any of them.
|
||||
|
||||
{% hint style="info" %}
|
||||
The intelligence layer is not part of the client application. It performs analysis, coordination, and trust scoring separately from the frontend.
|
||||
{% endhint %}
|
||||
|
||||
---
|
||||
|
||||
## Real-Time Communication
|
||||
|
||||
OpenHuman maintains a persistent connection between the application and the intelligence layer. This means responses arrive in real time as they are generated. You see outputs streaming, not loading.
|
||||
|
||||
The connection is designed for resilience. If the network drops, OpenHuman reconnects automatically with progressive backoff. There is no manual reconnection process.
|
||||
|
||||
---
|
||||
|
||||
## Offline Behavior
|
||||
|
||||
OpenHuman's local state persists on your device. Your preferences, settings, and connected source configurations remain available even when you are offline.
|
||||
|
||||
Full analysis and intelligence features require a network connection, since they depend on the intelligence layer. When connectivity is restored, the app resumes normal operation without requiring you to re-authenticate or reconfigure.
|
||||
@@ -1,44 +0,0 @@
|
||||
---
|
||||
description: >-
|
||||
The Intelligence tab shows what OpenHuman knows and how its memory tree is
|
||||
growing. Open it from the bottom navigation bar.
|
||||
icon: brain-circuit
|
||||
---
|
||||
|
||||
# Intelligence
|
||||
|
||||
### System Status
|
||||
|
||||
The top of the page shows the current system state (idle, ingesting, summarizing) and a **Run ingest** button to manually trigger a sync against any connected source.
|
||||
|
||||
### Memory Tree
|
||||
|
||||
The Memory tab surfaces the live state of the [Memory Tree](../features/obsidian-wiki/memory-tree.md):
|
||||
|
||||
| Metric | What it shows |
|
||||
| ------------------------- | -------------------------------------------------------------------------------------------------- |
|
||||
| **Storage** | Total size of `<workspace>/memory_tree/chunks.db` and the Obsidian vault. |
|
||||
| **Sources** | How many distinct sources have been ingested (one per Gmail label, Slack channel, document, etc.). |
|
||||
| **Chunks** | Total ≤3k-token chunks in the store. |
|
||||
| **Topics** | Number of topic trees materialized so far (per-entity summaries built from "hot" entities). |
|
||||
| **First / latest memory** | Timestamps of the oldest and newest chunks. |
|
||||
|
||||
### Memory Graph
|
||||
|
||||
A force-directed visualization of entities and their relationships, drawn from the Memory Tree's entity index. The graph grows as auto-fetch pulls more data; sparse early on, denser within a few days of usage.
|
||||
|
||||
### Obsidian vault
|
||||
|
||||
A **View vault in Obsidian** button opens `<workspace>/wiki/` directly via an `obsidian://open?path=...` deep link. See [Obsidian Wiki](../features/obsidian-wiki/). You can also open the folder in any file browser — it's just Markdown.
|
||||
|
||||
### Ingestion Activity
|
||||
|
||||
A heatmap showing ingest events over time, similar to a GitHub contribution graph. Useful for spotting periods where auto-fetch was idle (e.g. a connection broke and stopped syncing).
|
||||
|
||||
### Search & retrieval
|
||||
|
||||
A search bar over your Memory Tree. Source-scoped, topic-scoped or global queries are all supported, and any result links back to the underlying chunk file in your Obsidian vault for full provenance.
|
||||
|
||||
### Routing
|
||||
|
||||
The Intelligence tab also surfaces which model the agent is using per task — see [Automatic Model Routing](../features/model-routing.md) for the full picture of how `hint:reasoning`, `hint:fast`, etc. resolve to providers.
|
||||
@@ -1,33 +0,0 @@
|
||||
---
|
||||
description: >-
|
||||
OpenHuman communicates with you through messaging channels. You choose your
|
||||
default, and OpenHuman sends proactive messages, briefings, and responses
|
||||
there.
|
||||
icon: message-plus
|
||||
---
|
||||
|
||||
# Messaging Channels
|
||||
|
||||
#### Available Channels
|
||||
|
||||
**Telegram:** The primary messaging channel. Connect your Telegram account and OpenHuman can send and receive messages, manage chats, search history, create groups, and perform 80+ actions on your behalf. All actions run through your own encrypted credentials.
|
||||
|
||||
**Discord:** Send and receive messages via Discord. Connect your account to receive OpenHuman messages in Discord.
|
||||
|
||||
**Web:** A browser-based chat interface within the OpenHuman desktop app. Messages stay entirely local.
|
||||
|
||||
#### Setting Your Default Channel
|
||||
|
||||
Navigate to Settings > Automation & Channels > Messaging Channels. Select your preferred default (Telegram, Discord, or Web). The active route status shows which channel is currently in use.
|
||||
|
||||
#### Configuring a Channel
|
||||
|
||||
Click the channel name in Channel Integrations to open its configuration page.
|
||||
|
||||
**Telegram** offers two credential modes: connect via OpenHuman (one-click, encrypted) or provide your own credentials for maximum control.
|
||||
|
||||
**Discord** follows a similar authorization pattern.
|
||||
|
||||
#### Telegram Capabilities
|
||||
|
||||
When connected, OpenHuman has access to 80+ Telegram actions. See the T[elegram Capabilities Reference](messaging-channels.md#telegram-capabilities) for the complete list.
|
||||
@@ -1,59 +0,0 @@
|
||||
---
|
||||
icon: screwdriver
|
||||
---
|
||||
|
||||
# Skills & Integrations
|
||||
|
||||
OpenHuman extends its capabilities through two surfaces: **third-party integrations** (third-party services) and **Skills** (custom logic that runs inside the app). Both are accessible from the Skills tab in the bottom navigation.
|
||||
|
||||
<figure><img src="../.gitbook/assets/9. Skills & Integrations@2x.png" alt=""><figcaption></figcaption></figure>
|
||||
|
||||
#### Third-party Integrations (118+)
|
||||
|
||||
OpenHuman ships with backend-proxied access to **over a hundred third-party services**. Each connects via a one-click OAuth flow and shows up as both an agent tool and a memory source.
|
||||
|
||||
A non-exhaustive sample of what's in the catalog:
|
||||
|
||||
| Category | Examples |
|
||||
| ----------------------- | ---------------------------------------------------------- |
|
||||
| **Email & calendar** | Gmail, Outlook, Google Calendar, Apple Calendar |
|
||||
| **Docs & storage** | Google Docs, Drive, Notion, Dropbox, Airtable |
|
||||
| **Code & dev** | GitHub, Linear, Jira, Figma |
|
||||
| **Comms** | Slack, Discord, Microsoft Teams, Telegram, WhatsApp |
|
||||
| **CRM & sales** | Salesforce, HubSpot |
|
||||
| **Commerce & payments** | Stripe, Shopify |
|
||||
| **Project management** | Asana, Trello |
|
||||
| **Social** | Twitter / X, Spotify, YouTube, Reddit, Facebook, Instagram |
|
||||
|
||||
Some toolkits — Gmail today, more in flight — also have **native providers** that ingest into the [Memory Tree](../features/obsidian-wiki/memory-tree.md) directly. See [Third-party Integrations](../features/integrations.md) for the full picture.
|
||||
|
||||
#### How connections work
|
||||
|
||||
Click **Connect** on any integration. A browser window opens for OAuth. Once you sign in, the connection becomes active and OpenHuman starts syncing it through [auto-fetch](../features/auto-fetch.md) on the next 5-minute tick.
|
||||
|
||||
Each integration shows its current status:
|
||||
|
||||
* **Not connected** — integration has not been set up.
|
||||
* **Connected** — integration is active and being synced.
|
||||
* **Manage** — active integration with options to reconfigure or disconnect.
|
||||
|
||||
You can revoke any connection at any time.
|
||||
|
||||
#### Native voice and tools
|
||||
|
||||
Two capabilities ship native rather than as integrations because they're load-bearing for the desktop experience:
|
||||
|
||||
* [**Voice**](../features/voice.md) — STT in, ElevenLabs TTS out, plus a live Google Meet agent that joins meetings, transcribes them into your Memory Tree, and can speak back into the call.
|
||||
* [**Native tools**](../features/native-tools.md) — built-in web search, web-fetch scraper, and a full filesystem/git/lint/test/grep coder toolset that the agent uses out of the box.
|
||||
|
||||
#### Skills
|
||||
|
||||
Skills are custom logic that runs inside OpenHuman — small, sandboxed modules that can fetch external data, run on a schedule, transform information, and respond to events. Each runs with enforced resource limits.
|
||||
|
||||
Skills install from the Skills tab and integrate with the same Memory Tree as everything else.
|
||||
|
||||
#### Privacy
|
||||
|
||||
Integration tokens are held by the OpenHuman backend, not stored in plaintext on your machine. The core never calls integration APIs directly — every integration request is proxied. Ingest results land in your local Memory Tree and Obsidian vault; the **chunks live on your machine**.
|
||||
|
||||
See [Privacy & Security](privacy-and-security.md) for the full boundary.
|
||||
@@ -1,33 +0,0 @@
|
||||
---
|
||||
icon: '5'
|
||||
---
|
||||
|
||||
# Community Managers & Moderators
|
||||
|
||||
Your community has scaled to hundreds or thousands of members. You cannot read every message. Sentiment shifts, conflicts, and manipulation happen faster than you can monitor.
|
||||
|
||||
#### The problem
|
||||
|
||||
Moderation at scale is reactive. You catch problems after they blow up. No systematic way to track community health over time. Burnout is constant.
|
||||
|
||||
#### How OpenHuman helps
|
||||
|
||||
**Aggregated community health.** Surfaces patterns across your entire community: topic trends, participation distribution, engagement, and sentiment.
|
||||
|
||||
**Early warning signals.** Coordinated negative sentiment, scam patterns, and unusual account activity surfaced before escalation.
|
||||
|
||||
**Contribution recognition.** Identifies constructive members based on behavioral evidence.
|
||||
|
||||
**Moderation context.** Full history on any member: past behavior, interaction patterns, whether an incident is isolated or part of a pattern.
|
||||
|
||||
#### Example prompts
|
||||
|
||||
* "How is community health this week vs. last week?"
|
||||
* "Flag any unusual patterns in the last 48 hours"
|
||||
* "Who are the top contributors this month?"
|
||||
* "Give me the full context on this member's activity"
|
||||
* "Are there any emerging conflicts I should be aware of?"
|
||||
|
||||
#### When this use case is strongest
|
||||
|
||||
Best when managing 100+ active members across multiple channels. If you can read every message, the setup overhead may not be worth it.
|
||||
@@ -1,32 +0,0 @@
|
||||
---
|
||||
icon: '4'
|
||||
---
|
||||
|
||||
# DAO and Web3 Communities
|
||||
|
||||
Your DAO makes governance decisions through messaging. Proposals are discussed in Telegram, debated in Discord, and eventually formalized in votes. As the community grows, institutional memory degrades.
|
||||
|
||||
#### The problem
|
||||
|
||||
Chat-based governance creates accountability gaps. Decisions are scattered across threads. Contributors come and go. The same debates repeat because nobody can find the original discussion.
|
||||
|
||||
#### How OpenHuman helps
|
||||
|
||||
**Decision preservation.** Decisions are extracted as structured records with attribution, rationale, and source context. Surfaceable months later.
|
||||
|
||||
**Contribution tracking.** Participation patterns tracked across conversations: who is active, constructive, and following through.
|
||||
|
||||
**Governance continuity.** New proposals automatically surface the original context of referenced past decisions.
|
||||
|
||||
**Community health signals.** Aggregated participation, topic trends, and early warning signals for governance fatigue.
|
||||
|
||||
#### Example prompts
|
||||
|
||||
* "What governance decisions were made in the last quarter?"
|
||||
* "Show me the discussion that led to the treasury allocation vote in March"
|
||||
* "Who were the most active contributors this month?"
|
||||
* "Have we discussed this topic before? Show me the context."
|
||||
|
||||
#### When this use case is strongest
|
||||
|
||||
Best when governance happens primarily in messaging with 50+ active participants. Smaller DAOs can usually keep context in their heads.
|
||||
@@ -1,34 +0,0 @@
|
||||
---
|
||||
icon: '6'
|
||||
---
|
||||
|
||||
# Distributed Teams
|
||||
|
||||
Your team makes decisions in chat and struggles to execute. Commitments dissolve into scroll history. Contradictions between separate conversations go unnoticed until standup.
|
||||
|
||||
<figure><img src="../.gitbook/assets/25. Distributed Teams@2x.png" alt=""><figcaption></figcaption></figure>
|
||||
|
||||
#### The problem
|
||||
|
||||
Chat is fast but ephemeral. Action items live in threads nobody revisits. Accountability depends on individual memory, which fails at scale.
|
||||
|
||||
#### How OpenHuman helps
|
||||
|
||||
**Cross-channel ingest, on autopilot.** [Auto-fetch](../features/auto-fetch.md) pulls from Slack, Discord, GitHub, Linear, Jira, Notion and Gmail every five minutes — every channel you've connected through [third-party integrations](../features/integrations.md) — and folds the results into a single [Memory Tree](../features/obsidian-wiki/memory-tree.md).
|
||||
|
||||
**Decision and action extraction.** The agent can pull commitments out of chat with attribution and source links. Because every chunk is also a `.md` file in your [Obsidian vault](../features/obsidian-wiki/), you can audit any extracted commitment back to the original message.
|
||||
|
||||
**Contradiction detection.** Topic trees aggregate per entity (per project, per ticket) across channels, so the agent can flag conflicting commitments before they become surprises.
|
||||
|
||||
**Structured exports.** Decisions and action items can be written straight into Notion or Google Sheets via the same same integration surface — no manual transcription.
|
||||
|
||||
#### Example prompts
|
||||
|
||||
* "What did we commit to this week across all channels?"
|
||||
* "Are there any contradictions between what different team members said about the Q4 launch?"
|
||||
* "Extract all action items from #product since Monday and write them to the team Notion."
|
||||
* "What decisions were made yesterday that need documenting?"
|
||||
|
||||
#### When this use case is strongest
|
||||
|
||||
Best when coordination happens through messaging across multiple channels with 5+ people. If your team uses a rigid project-management tool for _every_ commitment and chat is purely social, OpenHuman adds less value.
|
||||
@@ -1,42 +0,0 @@
|
||||
---
|
||||
icon: '2'
|
||||
---
|
||||
|
||||
# Individual Power Users
|
||||
|
||||
You live in messaging. Dozens of active Slack channels, Telegram groups, DMs, threads, plus an inbox that never quite hits zero. Every morning starts with a wall of unread context.
|
||||
|
||||
#### The problem
|
||||
|
||||
High-volume comms create information overload that gets worse as you join more groups. Reading everything is impossible; skimming means you miss decisions, commitments, and replies you owe people.
|
||||
|
||||
#### How OpenHuman helps
|
||||
|
||||
**Auto-fetch keeps the picture fresh.** Every five minutes, OpenHuman pulls new messages from your active [third-party integrations](../features/integrations.md) — Gmail, Slack, GitHub, Notion — and folds them into the [Memory Tree](../features/obsidian-wiki/memory-tree.md). By the time you sit down with coffee, the briefing is already pre-computed.
|
||||
|
||||
**Topic trees catch the things waiting on you.** As entities (people, projects) get more activity, their topic tree gets refreshed. "Things waiting on a reply from me" becomes a real query, not a manual scrub.
|
||||
|
||||
**TokenJuice keeps it cheap.** Sweeping through hundreds of emails for a daily briefing costs cents, not dollars, because [TokenJuice](../features/token-compression.md) compacts the noise before the model ever sees it.
|
||||
|
||||
**Your wiki is yours.** All the same data lands in `<workspace>/wiki/` as Markdown. Open it in [Obsidian](../features/obsidian-wiki/) when you want to wander through it by hand.
|
||||
|
||||
#### Example prompts
|
||||
|
||||
* "What do I need to know from the last 12 hours?"
|
||||
* "Are there any messages waiting for my response?"
|
||||
* "Summarize the key decisions from #engineering this week."
|
||||
* "Extract all action items assigned to me from the last 3 days."
|
||||
* "What did Sarah say about the Q4 roadmap across email and Slack?"
|
||||
|
||||
#### Features that matter most here
|
||||
|
||||
| Feature | Why it matters |
|
||||
| ----------------------------------------------------------------------- | --------------------------------------------------------- |
|
||||
| [Auto-fetch](../features/auto-fetch.md) | Your inbox/Slack/etc. lands in memory without you asking |
|
||||
| [Memory Tree topic summaries](../features/obsidian-wiki/memory-tree.md) | Per-entity recaps surface what's outstanding |
|
||||
| [TokenJuice](../features/token-compression.md) | Daily sweeps stay cheap even across thousands of messages |
|
||||
| [third-party integrations](../features/integrations.md) | One-click OAuth into 118+ services |
|
||||
|
||||
#### When this use case is strongest
|
||||
|
||||
You're juggling 10+ active conversations across at least two or three platforms. If you have two quiet channels and a tidy inbox, the manual approach works fine.
|
||||
@@ -1,56 +0,0 @@
|
||||
---
|
||||
icon: '1'
|
||||
---
|
||||
|
||||
# Knowledge Workers
|
||||
|
||||
You work across 8+ applications every day. Email, Slack, spreadsheets, documents, design tools, project boards, your code editor. Each app switch costs you context. By the end of the day, the thread connecting your morning research to your afternoon decisions has frayed. You spend as much time reconstructing context as you do acting on it.
|
||||
|
||||
OpenHuman turns that fragmented workflow into cumulative awareness.
|
||||
|
||||
#### The problem
|
||||
|
||||
Context evaporates with every app switch. You review a spreadsheet at 10am, discuss it in Slack at 11am, and by 2pm when you need to write a summary email, you have to reopen the spreadsheet, re-read the Slack thread, and piece it all back together.
|
||||
|
||||
Your brain does this all day. It is exhausting, and things fall through the cracks.
|
||||
|
||||
#### How OpenHuman helps
|
||||
|
||||
**Auto-fetch builds a continuous record.** Every five minutes, OpenHuman pulls fresh data from every active [integration](../features/integrations.md) — Gmail, Slack, GitHub, Notion, Drive, Linear, Jira, Calendar — and folds the results into the [Memory Tree](../features/obsidian-wiki/memory-tree.md). You don't log anything manually; the picture builds itself in the background.
|
||||
|
||||
**Topic trees bridge your tools.** When you ask "what happened with the Q3 projections today?", you get a unified answer spanning every connected source — because the topic tree for "Q3 projections" was built from all of them.
|
||||
|
||||
**Your wiki is yours.** Everything also lands as `.md` in `<workspace>/wiki/`. Open it in [Obsidian](../features/obsidian-wiki/) at the end of the day to skim, or drop in your own meeting notes — they get ingested into the same trees.
|
||||
|
||||
**TokenJuice keeps it cheap.** Sweeping through dozens of long email threads or a busy Slack channel for a daily roll-up costs cents, not dollars, because [TokenJuice](../features/token-compression.md) compacts the noise before the model sees it.
|
||||
|
||||
#### Example prompts
|
||||
|
||||
* "What did I miss while I was heads-down this morning?"
|
||||
* "Summarize everything related to Project Atlas across email, Slack and Jira."
|
||||
* "What did the team discuss about the launch timeline while I was heads-down in the doc?"
|
||||
* "Connect what Sarah said in Slack with the spreadsheet I was reviewing."
|
||||
* "What did I commit to today, with sources?"
|
||||
|
||||
#### Features that matter most here
|
||||
|
||||
| Feature | Why it matters |
|
||||
| ------------------------------------------------------- | -------------------------------------------------------------- |
|
||||
| [Auto-fetch](../features/auto-fetch.md) | Continuous ingest from every connected tool, no manual logging |
|
||||
| [Memory Tree](../features/obsidian-wiki/memory-tree.md) | Per-topic + per-day summaries that span every source |
|
||||
| [third-party integrations](../features/integrations.md) | One-click OAuth into 118+ services |
|
||||
| [Obsidian Wiki](../features/obsidian-wiki/) | Audit and edit your memory by hand, in plain Markdown |
|
||||
|
||||
#### A typical workflow
|
||||
|
||||
**Morning:** You open your laptop. Auto-fetch quietly catches up on Gmail, Slack and GitHub from overnight. The first thing you ask is "what's waiting on me?" and get a 30-second briefing.
|
||||
|
||||
**Midday:** You jump from Slack to Notion to a code review. You don't have to remember each detail — the topic tree for the project absorbs it all.
|
||||
|
||||
**Afternoon:** You're drafting a status update. You ask "what did the team commit to this week, with attribution?" and get a clean list pulled from #engineering, #product and Linear.
|
||||
|
||||
**End of day:** You open the Obsidian vault and skim today's global digest. If something is wrong, you fix the Markdown by hand and the agent sees the correction next ingest.
|
||||
|
||||
#### When this use case is strongest
|
||||
|
||||
Knowledge Workers get the most value when they work across many applications daily and frequently need to reference earlier work. If your workflow is single-app, OpenHuman's cross-source advantage matters less.
|
||||
@@ -1,57 +0,0 @@
|
||||
---
|
||||
icon: grid-4
|
||||
---
|
||||
|
||||
# Overview
|
||||
|
||||
OpenHuman shines wherever you already operate across many tools and conversations and feel the cost of context loss between them. The underlying system is the same; the value just looks different in different environments.
|
||||
|
||||
***
|
||||
|
||||
## Individual Power Users
|
||||
|
||||
You live across Gmail, Slack, Discord, Notion, GitHub, Linear and a dozen Telegram groups. Things slip. The Memory Tree quietly absorbs all of it via [auto-fetch](../features/auto-fetch.md), and one question — "what do I need to know from the last 12 hours?" — gives you a 30-second briefing across the whole picture.
|
||||
|
||||
The value here is personal clarity and reduced cognitive load.
|
||||
|
||||
***
|
||||
|
||||
## Traders and Analysts
|
||||
|
||||
You use chat as a real-time information source. OpenHuman extracts claims, ties them to source messages in your Obsidian vault, and tracks how they resolve over time. Over weeks, you see who is consistently right and who isn't — grounded in a memory you can audit by hand.
|
||||
|
||||
The outcome is better information filtering, not automated trading.
|
||||
|
||||
***
|
||||
|
||||
## DAO and Web3 Communities
|
||||
|
||||
DAOs run on chat but accountability needs structure. The Memory Tree preserves decisions and contributions with provenance. Topic trees give moderators an entity-level view of who is participating in what.
|
||||
|
||||
The value is continuity and accountability without forcing discussions into formal tools too early.
|
||||
|
||||
***
|
||||
|
||||
## Community Managers and Moderators
|
||||
|
||||
Aggregated views of group health, contribution patterns, and risk signals — surfaced from the same Memory Tree your agent reasons over. OpenHuman doesn't replace moderation judgment; it gives you context and early warnings.
|
||||
|
||||
***
|
||||
|
||||
## Distributed Teams
|
||||
|
||||
Decisions live in chat. OpenHuman extracts actions and decisions across channels into structured records you can drop into Notion or Google Sheets via [third-party integrations](../features/integrations.md). Cross-channel topic trees catch contradictions ("Alex committed to Friday in #engineering, but Maria flagged Monday in #design") before they become standup surprises.
|
||||
|
||||
***
|
||||
|
||||
## Service Providers and Contributors
|
||||
|
||||
Reputation across communities is hard when history is fragmented. The Obsidian vault is a durable, portable record of your contributions and the surrounding context — yours to keep, with or without OpenHuman.
|
||||
|
||||
***
|
||||
|
||||
## When OpenHuman Is Not a Good Fit
|
||||
|
||||
OpenHuman provides limited value in environments that are purely social or intentionally ephemeral, or for high-frequency trading execution and strict-real-time use cases. The Memory Tree is also less useful when the underlying data is intentionally throwaway (memes, ad-hoc voice chats with no transcript).
|
||||
|
||||
Understanding these boundaries helps you apply OpenHuman where it's strongest.
|
||||
@@ -1,30 +0,0 @@
|
||||
---
|
||||
icon: '7'
|
||||
---
|
||||
|
||||
# Service Providers & Contributors
|
||||
|
||||
You work across multiple communities and projects as a freelancer, consultant, or contributor. Your track record is your most valuable asset, but it is fragmented across platforms.
|
||||
|
||||
#### The problem
|
||||
|
||||
Reputation in decentralized ecosystems is hard to build. Your best work is buried in private channels and resolved issues. Proving reliability to new clients means assembling scattered evidence manually.
|
||||
|
||||
#### How OpenHuman helps
|
||||
|
||||
**Behavioral contribution tracking.** Participation patterns tracked over time: delivery consistency, engagement quality, expertise areas, and follow-through.
|
||||
|
||||
**Portable credibility.** Contribution history moves with you across communities.
|
||||
|
||||
**Scope documentation.** Decisions, deliverables, and outcomes extracted from project conversations automatically.
|
||||
|
||||
#### Example prompts
|
||||
|
||||
* "Summarize my contributions across all communities this month"
|
||||
* "What projects have I delivered on in the last quarter?"
|
||||
* "What commitments have I made that are still open?"
|
||||
* "Draft a project summary from my conversations with the Meridian team"
|
||||
|
||||
#### When this use case is strongest
|
||||
|
||||
Best when working across 3+ communities or projects simultaneously. If you work with one team on one project, a personal log may suffice.
|
||||
@@ -1,43 +0,0 @@
|
||||
---
|
||||
icon: '3'
|
||||
---
|
||||
|
||||
# Traders and Analysts
|
||||
|
||||
You monitor multiple Telegram groups, Discord channels, Twitter feeds, and on-chain activity for alpha. The noise-to-signal ratio is brutal.
|
||||
|
||||
#### The problem
|
||||
|
||||
Trading groups move fast. By the time you verify a claim, the opportunity has passed. Acting on unverified claims is how you lose money. Scams and coordinated manipulation are constant threats.
|
||||
|
||||
#### How OpenHuman helps
|
||||
|
||||
**Claim tracking with resolution.** Predictions are extracted as structured claims with resolution dates. Over time, you build track records for every participant.
|
||||
|
||||
**Risk intelligence.** Behavioral pattern recognition flags coordinated message timing, impersonation, and repeated failure patterns across groups.
|
||||
|
||||
**Historical context at query speed.** "What did this group say about SOL in January?" returns compressed history from months of indexed data.
|
||||
|
||||
**On-chain + off-chain fusion.** When wallet data is connected, the Memory Tree's topic trees correlate on-chain activity with off-chain discussions across the same entities.
|
||||
|
||||
#### Example prompts
|
||||
|
||||
* "What claims were made about ETH this week and who made them?"
|
||||
* "Show me the track record of @cryptoalpha in the trading group"
|
||||
* "Flag any suspicious patterns across my groups in the last 24 hours"
|
||||
* "Summarize the alpha from my top 5 groups today"
|
||||
* "Who has been consistently accurate about L2 predictions?"
|
||||
|
||||
#### Features that matter most here
|
||||
|
||||
| Feature | Why it matters |
|
||||
| ------------------------------ | --------------------------------------------------------------- |
|
||||
| Claim tracking | Predictions become verifiable records |
|
||||
| Risk intelligence | Flags scam patterns and coordinated abuse |
|
||||
| Temporal context | Recalls historical discussions and correlates with price action |
|
||||
| Cross-source entity resolution | Links on-chain events with off-chain conversations |
|
||||
| Semantic deduplication | Strips repetitive alpha regurgitation |
|
||||
|
||||
#### When this use case is strongest
|
||||
|
||||
Best when monitoring 5+ information sources simultaneously. If you trade on technical analysis alone without social signals, this adds less value.
|
||||
Reference in New Issue
Block a user