mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-27 21:08:00 +00:00
docs: consolidate CLAUDE.md into AGENTS.md symlink (#3419)
This commit is contained in:
@@ -4,8 +4,7 @@ This directory is intentionally near-empty.
|
||||
|
||||
Authoritative docs for AI agents and contributors:
|
||||
|
||||
- **[`CLAUDE.md`](../../CLAUDE.md)** — repo layout, runtime scope, commands, frontend/Tauri/Rust conventions, testing, debug logging, feature workflow.
|
||||
- **[`AGENTS.md`](../../AGENTS.md)** — RPC controller patterns, `RpcOutcome<T>` contract.
|
||||
- **[`AGENTS.md`](../../AGENTS.md)** — all project conventions, commands, architecture, testing, patterns. (`CLAUDE.md` redirects here.)
|
||||
- **[`.claude/memory.md`](../memory.md)** — project memory: fixes, gotchas, strict rules, workflow notes.
|
||||
- **[`gitbooks/developing/architecture.md`](../../gitbooks/developing/architecture.md)** — narrative architecture, dual-socket sync.
|
||||
- **[`gitbooks/developing/architecture/frontend.md`](../../gitbooks/developing/architecture/frontend.md)** — frontend layout.
|
||||
@@ -18,6 +17,6 @@ Authoritative docs for AI agents and contributors:
|
||||
|
||||
## When to add a file here
|
||||
|
||||
Only add a `*.md` file in this directory if you need **path-gated context** loaded conditionally by Claude Code (via the `paths:` frontmatter) for a narrow part of the tree, AND the content is not already covered in `CLAUDE.md`.
|
||||
Only add a `*.md` file in this directory if you need **path-gated context** loaded conditionally by Claude Code (via the `paths:` frontmatter) for a narrow part of the tree, AND the content is not already covered in `AGENTS.md`.
|
||||
|
||||
Each file added here ships in every agent context that matches its `paths:` glob — so keep them small, current, and non-overlapping with `CLAUDE.md`. Stale rules actively mislead agents.
|
||||
Each file added here ships in every agent context that matches its `paths:` glob — so keep them small, current, and non-overlapping with `AGENTS.md`. Stale rules actively mislead agents.
|
||||
|
||||
@@ -2,657 +2,294 @@
|
||||
|
||||
**AI assistant for communities — React + Tauri v2 desktop app with a Rust core (JSON-RPC / CLI) embedded in-process.**
|
||||
|
||||
This file orients contributors and coding agents. Authoritative narrative architecture: [`gitbooks/developing/architecture.md`](gitbooks/developing/architecture.md). Frontend layout: [`gitbooks/developing/architecture/frontend.md`](gitbooks/developing/architecture/frontend.md). Tauri shell: [`gitbooks/developing/architecture/tauri-shell.md`](gitbooks/developing/architecture/tauri-shell.md).
|
||||
Architecture docs: [`gitbooks/developing/architecture.md`](gitbooks/developing/architecture.md) | [Frontend](gitbooks/developing/architecture/frontend.md) | [Tauri shell](gitbooks/developing/architecture/tauri-shell.md) | [Agent harness](gitbooks/developing/architecture/agent-harness.md)
|
||||
|
||||
---
|
||||
|
||||
## Repository layout
|
||||
|
||||
| Path | Role |
|
||||
| ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`app/`** | pnpm workspace **`openhuman-app`** (v0.53.45): Vite + React (`app/src/`), Tauri desktop host (`app/src-tauri/`), Vitest tests |
|
||||
| **Repo root `src/`** | Rust library crate **`openhuman`** (lib name) with **`openhuman-core`** CLI binary entrypoint (`src/main.rs`) — `src/core/` (transport: HTTP/JSON-RPC, CLI, dispatch, auth, event bus), `src/openhuman/*` domains. The QuickJS skills runtime has been removed; `src/openhuman/skills/` is metadata-only now. |
|
||||
| **Skills registry** | **[`tinyhumansai/openhuman-skills`](https://github.com/tinyhumansai/openhuman-skills)** on GitHub — canonical skill packages and TS build; not vendored in this tree. |
|
||||
| **`Cargo.toml`** (root) | Core crate; `cargo build --bin openhuman-core` produces the CLI binary. Helper binaries: `slack-backfill`, `gmail-backfill-3d` in `src/bin/`. |
|
||||
| **`docs/`** | Architecture and deep-internal references |
|
||||
| **`gitbooks/developing/`** | Public contributor docs — frontend, Tauri shell, testing, release, agent harness, CEF, observability |
|
||||
| Path | Role |
|
||||
| ----------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`app/`** | pnpm workspace `openhuman-app`: Vite + React (`app/src/`), Tauri desktop host (`app/src-tauri/`), Vitest tests |
|
||||
| **`src/`** (root) | Rust lib crate `openhuman` + `openhuman-core` CLI binary (`src/main.rs`) — `src/core/` (transport), `src/openhuman/*` domains |
|
||||
| **`Cargo.toml`** (root) | Core crate; `cargo build --bin openhuman-core`. Also `slack-backfill` and `gmail-backfill-3d` in `src/bin/`. |
|
||||
| **`docs/`** | Deep internals. Public contributor docs in `gitbooks/developing/`. |
|
||||
|
||||
Commands in documentation assume the **repo root** unless noted: `pnpm dev` runs Vite-only inside the `app` workspace; `pnpm dev:app` runs the full Tauri desktop dev (CEF runtime).
|
||||
|
||||
**Skills registry:** Skill sources and the bundler live in **[github.com/tinyhumansai/openhuman-skills](https://github.com/tinyhumansai/openhuman-skills)**. The desktop app's skills catalog defaults to that GitHub slug; override with `VITE_SKILLS_GITHUB_REPO` (see [`app/src/utils/config.ts`](app/src/utils/config.ts)). Note: since the QuickJS runtime was removed, the desktop app no longer *executes* skill packages — it only discovers, installs metadata, and renders catalog entries. Skill execution surfaces are being rebuilt; check the current domain modules before assuming a skill can run end-to-end.
|
||||
Commands assume **repo root**. Root `package.json` is `openhuman-repo` (private, pnpm-enforced).
|
||||
|
||||
---
|
||||
|
||||
## Runtime scope
|
||||
|
||||
- **Shipped product**: desktop — Windows, macOS, Linux (see [`gitbooks/developing/architecture.md`](gitbooks/developing/architecture.md) "Platform reach").
|
||||
- **Tauri host** (`app/src-tauri`): **desktop-only**. Do not add Android/iOS branches.
|
||||
- **Core runs in-process** as a tokio task inside the Tauri host (sidecar removed in PR #1061). The host owns its lifetime via `core_process::CoreProcessHandle` in `app/src-tauri/src/core_process.rs`. Frontend RPC still goes over HTTP to `http://127.0.0.1:<port>/rpc` authenticated with a per-launch hex bearer; the Tauri shell generates it in `CoreProcessHandle::new()` and hands it to the embedded server in-memory via `run_server_embedded_with_ready(rpc_token: Some(_))` — `OPENHUMAN_CORE_TOKEN` is no longer set on the process env by the desktop shell (CLI / docker / cloud env-as-config is preserved). The Tauri command `core_rpc_token` exposes the bearer to the renderer. Set `OPENHUMAN_CORE_REUSE_EXISTING=1` to attach to an externally-started `openhuman-core` process for debugging.
|
||||
- **Shipped product**: desktop — Windows, macOS, Linux. No Android/iOS in the Tauri host.
|
||||
- **Core runs in-process** as a tokio task (sidecar removed PR #1061). Lifecycle: `core_process::CoreProcessHandle` in `app/src-tauri/src/core_process.rs`. Frontend RPC → `http://127.0.0.1:<port>/rpc` with per-launch hex bearer handed in-memory via `run_server_embedded_with_ready(rpc_token: Some(_))`. Renderer reads bearer via `core_rpc_token` Tauri command. `OPENHUMAN_CORE_TOKEN` still honoured for CLI/docker/cloud. Set `OPENHUMAN_CORE_REUSE_EXISTING=1` for external core debugging.
|
||||
|
||||
**Where logic lives**
|
||||
**Where logic lives:**
|
||||
|
||||
- **Rust (`openhuman` / repo root `src/`)**: **Business logic and execution**—domains, RPC, persistence, CLI behavior. Authoritative.
|
||||
- **Tauri + React (`app/`)**: **Interaction and UX**—screens, navigation, input, accessibility, windowing, and bridging to the in-process core. The shell presents and orchestrates; it does not duplicate core business rules.
|
||||
- **Rust core** (`src/`): business logic, execution, domains, RPC, persistence, CLI. Authoritative.
|
||||
- **Tauri + React** (`app/`): UX, screens, navigation, bridging. Presents and orchestrates only.
|
||||
|
||||
---
|
||||
|
||||
## Commands (from repository root)
|
||||
## iOS client (experimental, non-shipping)
|
||||
|
||||
Connects to desktop core via `ConnectionProfile` transport strategies in `app/src/services/transport/`: `LanHttpTransport`, `TunnelTransport` (E2E encrypted XChaCha20-Poly1305), `CloudHttpTransport`. Key paths: PTT plugin `packages/tauri-plugin-ptt/`, iOS screens `app/src/pages/ios/`, devices domain `src/openhuman/devices/`, tunnel crypto `app/src/lib/tunnel/`. Build: `pnpm tauri:ios:dev` (stock `@tauri-apps/cli`, not vendored CEF). Backend dep: `tinyhumansai/backend#709`.
|
||||
|
||||
---
|
||||
|
||||
## Commands (from repo root)
|
||||
|
||||
```bash
|
||||
# Vite dev only (no Tauri host)
|
||||
pnpm dev
|
||||
pnpm dev # Vite dev server only
|
||||
pnpm dev:app # Full Tauri desktop dev (CEF, loads env via scripts/load-dotenv.sh)
|
||||
pnpm build # Production UI build
|
||||
pnpm typecheck # tsc --noEmit (alias: compile)
|
||||
pnpm lint # ESLint --cache
|
||||
pnpm format # Prettier write + cargo fmt
|
||||
pnpm format:check # Prettier check + cargo fmt --check
|
||||
|
||||
# Full desktop with Tauri/CEF (loads env via scripts/load-dotenv.sh)
|
||||
pnpm dev:app
|
||||
|
||||
# Production UI build (app workspace)
|
||||
pnpm build
|
||||
|
||||
# Typecheck / lint / format
|
||||
pnpm typecheck # alias for `pnpm compile` (tsc --noEmit)
|
||||
pnpm lint
|
||||
pnpm format # Prettier + cargo fmt
|
||||
pnpm format:check
|
||||
|
||||
# `pnpm core:stage` is a no-op — sidecar removed in PR #1061; core is in-process.
|
||||
|
||||
# Skills — author / build in the registry repo (tinyhumansai/openhuman-skills).
|
||||
# There are no `skills:build` / `skills:watch` scripts in this repo's app workspace.
|
||||
|
||||
# Rust — core library + CLI (repo root)
|
||||
# Rust
|
||||
cargo check --manifest-path Cargo.toml
|
||||
cargo build --manifest-path Cargo.toml --bin openhuman-core
|
||||
cargo check --manifest-path app/src-tauri/Cargo.toml # or: pnpm rust:check
|
||||
|
||||
# Rust — Tauri shell only
|
||||
cargo check --manifest-path app/src-tauri/Cargo.toml
|
||||
pnpm rust:check # same as above
|
||||
|
||||
# whisper-rs / llama.cpp on macOS Tahoe (Apple Silicon) fail with `-mcpu=native`.
|
||||
# Workaround for `cargo check`/`cargo test`:
|
||||
# macOS Apple Silicon workaround (whisper-rs / llama.cpp)
|
||||
GGML_NATIVE=OFF cargo check --manifest-path Cargo.toml
|
||||
|
||||
# PR maintenance
|
||||
pnpm pr:sync-main --help # inspect options
|
||||
pnpm pr:sync-main # dry-run: scan open PRs targeting main
|
||||
pnpm pr:sync-main --execute # merge latest main into each matching PR branch and push
|
||||
```
|
||||
|
||||
**Tests**: Vitest in `app/` (`pnpm test`, `pnpm test:coverage`); Rust via `pnpm test:rust` (runs `scripts/test-rust-with-mock.sh`).
|
||||
`pnpm core:stage` is a no-op (sidecar removed).
|
||||
|
||||
**Quality**: ESLint + Prettier + Husky in the `app` workspace. Pre-push hook runs `pnpm rust:check`. Use `--no-verify` only for unrelated pre-existing breakage and call it out in the PR body.
|
||||
|
||||
### Codex web / Linear-launched PR checklist
|
||||
|
||||
Before opening AI-authored PRs from Codex web sessions or Linear-launched implementation agents, follow [`docs/agent-workflows/codex-pr-checklist.md`](docs/agent-workflows/codex-pr-checklist.md).
|
||||
|
||||
This checklist is required for remote agents because OpenHuman has several merge gates that are easy to miss in partial environments: Prettier, Rust formatting, TypeScript typecheck, focused Vitest coverage, controller dispatch parity, and Tauri vendored dependency availability. If a command cannot run in the remote environment, the PR body must report the exact blocked command and error instead of claiming validation passed.
|
||||
**Tests**: `pnpm test` (Vitest) · `pnpm test:coverage` · `pnpm test:rust` (`scripts/test-rust-with-mock.sh`).
|
||||
**Quality**: ESLint + Prettier + Husky. Pre-push hook runs `pnpm rust:check`.
|
||||
|
||||
### Agent debug runners (`scripts/debug/`)
|
||||
|
||||
Use these wrappers instead of invoking Vitest / WDIO / cargo directly when iterating — they keep stdout summary-sized and tee full output to `target/debug-logs/<kind>-<suffix>-<timestamp>.log`. Add `--verbose` to also stream raw output. See [`scripts/debug/README.md`](scripts/debug/README.md).
|
||||
Summary-sized stdout; full output teed to `target/debug-logs/`. Add `--verbose` to stream raw.
|
||||
|
||||
```bash
|
||||
# Vitest
|
||||
pnpm debug unit # full suite
|
||||
pnpm debug unit src/components/Foo.test.tsx # one file (positional pattern)
|
||||
pnpm debug unit -t "renders empty state" # filter by test name
|
||||
pnpm debug unit Foo -t "renders empty" --verbose
|
||||
|
||||
# WDIO E2E (one spec at a time)
|
||||
pnpm debug e2e test/e2e/specs/smoke.spec.ts
|
||||
pnpm debug e2e test/e2e/specs/cron-jobs-flow.spec.ts cron-jobs --verbose
|
||||
|
||||
# cargo tests (delegates to scripts/test-rust-with-mock.sh)
|
||||
pnpm debug rust
|
||||
pnpm debug rust json_rpc_e2e
|
||||
|
||||
# Inspect saved logs
|
||||
pnpm debug logs # list 50 most recent
|
||||
pnpm debug logs last # print most recent (last 400 lines)
|
||||
pnpm debug logs unit # most recent matching prefix "unit"
|
||||
pnpm debug logs last --tail 100
|
||||
pnpm debug unit # full Vitest suite
|
||||
pnpm debug unit src/components/Foo.test.tsx # one file
|
||||
pnpm debug unit -t "renders empty state" # filter by name
|
||||
pnpm debug e2e test/e2e/specs/smoke.spec.ts # WDIO E2E
|
||||
pnpm debug rust # cargo tests
|
||||
pnpm debug rust json_rpc_e2e # targeted
|
||||
pnpm debug logs # list recent
|
||||
pnpm debug logs last # print most recent
|
||||
```
|
||||
|
||||
Files: `scripts/debug/{cli,unit,e2e,rust,logs,lib}.sh`. Entry point: `pnpm debug` (`scripts/debug/cli.sh`).
|
||||
|
||||
### Coverage requirement (merge gate)
|
||||
|
||||
PRs must meet **≥ 80% coverage on changed lines**. Enforced by [`.github/workflows/coverage.yml`](.github/workflows/coverage.yml) via `diff-cover` over merged Vitest + `cargo-llvm-cov` (core + Tauri shell) lcov outputs. Below the threshold the PR will not merge. Run `pnpm test:coverage` and `pnpm test:rust` locally; add tests for new/changed lines (happy path + at least one failure / edge case).
|
||||
PRs need **≥ 80% coverage on changed lines** via `diff-cover` over Vitest + `cargo-llvm-cov` lcov. Enforced by `.github/workflows/coverage.yml`.
|
||||
|
||||
---
|
||||
|
||||
## Configuration
|
||||
|
||||
Environment variables are documented in two `.env.example` files:
|
||||
- **[`.env.example`](.env.example)** — Rust core, Tauri shell, backend URL, logging. Load: `source scripts/load-dotenv.sh`.
|
||||
- **[`app/.env.example`](app/.env.example)** — `VITE_*` vars. Copy to `app/.env.local`.
|
||||
- **Frontend config** centralized in [`app/src/utils/config.ts`](app/src/utils/config.ts) — never read `import.meta.env` directly elsewhere.
|
||||
- **Rust config**: TOML `Config` struct (`src/openhuman/config/schema/types.rs`) with env overrides (`load.rs`).
|
||||
|
||||
- **[`.env.example`](.env.example)** (repo root) — Rust core, Tauri shell, backend URL, logging, proxy, storage, web search, local AI binary overrides. Loaded via `source scripts/load-dotenv.sh`.
|
||||
- **[`app/.env.example`](app/.env.example)** — Frontend `VITE_*` vars (core RPC URL, backend URL, Sentry DSN, skills repo, dev helpers). Copy to `app/.env.local` for local overrides.
|
||||
### Agent access & security
|
||||
|
||||
**Frontend config** is centralized in [`app/src/utils/config.ts`](app/src/utils/config.ts). All `VITE_*` env vars should be read there and re-exported — do not read `import.meta.env` directly in other files.
|
||||
The `[autonomy]` block (`src/openhuman/config/schema/autonomy.rs`) drives `SecurityPolicy` (`src/openhuman/security/policy.rs`). Tiers: `readonly` / `supervised` / `full` × `workspace_only` × `trusted_roots` × `allow_tool_install`. Edit via `config.update_autonomy_settings` RPC or Settings → Agent access.
|
||||
|
||||
**Rust config** uses a TOML-based `Config` struct (`src/openhuman/config/schema/types.rs`) with env var overrides applied in `src/openhuman/config/schema/load.rs`. Env vars override config file values at runtime (e.g. `OPENHUMAN_API_URL` overrides `config.api_url`).
|
||||
**Two path roots** (`src/openhuman/config/schema/types.rs`):
|
||||
|
||||
- **`action_dir`** — agent's read/write root. Acting tools resolve relative paths here. Default: `~/OpenHuman/projects` (`OPENHUMAN_ACTION_DIR`).
|
||||
- **`workspace_dir`** — internal state (`~/.openhuman/users/<id>/workspace`). Agent tools **cannot** write here — enforced by `is_workspace_internal_path` fail-closed regardless of tier/trusted_roots.
|
||||
|
||||
**Command permission model**: `classify_command` → `CommandClass` (`Read`/`Write`/`Network`/`Install`/`Destructive`); unrecognized = `Write`. `gate_decision(class, tier)` → `Allow`/`Prompt`/`Block`. System/credential dirs unconditionally blocked (`is_always_forbidden`).
|
||||
|
||||
**Approval gate** ON by default (opt out: `OPENHUMAN_APPROVAL_GATE=0`). Parks interactive chat turns only; background/cron allowed through. Frontend surfaces via `ApprovalRequestCard`. 10-min TTL → Deny.
|
||||
|
||||
**Sandbox backends** (opt-in per agent via `sandbox_mode = "sandboxed"`): Docker (remote/cron), Local OS jail (Landlock/Seatbelt/AppContainer, desktop), Noop fallback. In-Rust path hardening applies regardless.
|
||||
|
||||
---
|
||||
|
||||
## Testing Guide (Unit + E2E)
|
||||
## Testing
|
||||
|
||||
### Unit tests (Vitest)
|
||||
### Unit (Vitest)
|
||||
|
||||
- **Where tests live**: co-locate as `*.test.ts` / `*.test.tsx` under `app/src/**`.
|
||||
- **Runner/config**: Vitest with `app/test/vitest.config.ts` and shared setup in `app/src/test/setup.ts`.
|
||||
- **Run**:
|
||||
- Co-locate as `*.test.ts(x)` under `app/src/**`. Config: `app/test/vitest.config.ts`.
|
||||
- Run: `pnpm test` or `pnpm test:coverage`. Prefer behavior over implementation. No real network, no time flakes.
|
||||
|
||||
```bash
|
||||
pnpm test:unit
|
||||
pnpm test:coverage
|
||||
```
|
||||
### Shared mock backend
|
||||
|
||||
- **Authoring rules**:
|
||||
- Prefer testing behavior over implementation details.
|
||||
- Use existing helpers from `app/src/test/` (`test-utils.tsx`, shared mock backend) before adding new harness code.
|
||||
- Keep tests deterministic: avoid real network calls, time-sensitive flakes, or hidden global state.
|
||||
- Core: `scripts/mock-api-core.mjs` · Server: `scripts/mock-api-server.mjs` · E2E: `app/test/e2e/mock-server.ts`.
|
||||
- Admin: `GET /__admin/health`, `POST /__admin/reset`, `POST /__admin/behavior`, `GET /__admin/requests`.
|
||||
- Manual: `pnpm mock:api`.
|
||||
|
||||
### Shared mock backend (app + Rust tests)
|
||||
|
||||
- **Core implementation**: `scripts/mock-api-core.mjs`
|
||||
- **Standalone server entrypoint**: `scripts/mock-api-server.mjs`
|
||||
- **E2E wrapper**: `app/test/e2e/mock-server.ts`
|
||||
- **Vitest unit setup**: `app/src/test/setup.ts` starts the shared mock server by default on `http://127.0.0.1:5005`.
|
||||
|
||||
Key admin endpoints:
|
||||
|
||||
- `GET /__admin/health`
|
||||
- `POST /__admin/reset`
|
||||
- `POST /__admin/behavior`
|
||||
- `GET /__admin/requests`
|
||||
|
||||
Run manually:
|
||||
|
||||
```bash
|
||||
pnpm mock:api
|
||||
curl -s http://127.0.0.1:18473/__admin/health
|
||||
```
|
||||
|
||||
### E2E tests (WDIO — dual platform)
|
||||
### E2E (WDIO — dual platform)
|
||||
|
||||
Full guide: [`gitbooks/developing/e2e-testing.md`](gitbooks/developing/e2e-testing.md).
|
||||
|
||||
Two automation backends:
|
||||
- **Linux (CI default)**: `tauri-driver` (WebDriver, port 4444) — drives the debug binary directly
|
||||
- **macOS (local dev)**: Appium Mac2 (XCUITest, port 4723) — drives the `.app` bundle
|
||||
- **Linux (CI)**: `tauri-driver` (WebDriver :4444). **macOS (local)**: Appium Mac2 (XCUITest :4723).
|
||||
- Specs: `app/test/e2e/specs/*.spec.ts`. Use `element-helpers.ts` helpers, never raw `XCUIElementType*`.
|
||||
- `e2e-run-spec.sh` creates/cleans temp `OPENHUMAN_WORKSPACE` by default.
|
||||
|
||||
- **Where specs live**: `app/test/e2e/specs/*.spec.ts`
|
||||
- **Shared harness**:
|
||||
- Platform detection: `app/test/e2e/helpers/platform.ts`
|
||||
- Element helpers: `app/test/e2e/helpers/element-helpers.ts`
|
||||
- Deep link helpers: `app/test/e2e/helpers/deep-link-helpers.ts`
|
||||
- App lifecycle: `app/test/e2e/helpers/app-helpers.ts`
|
||||
- Mock backend: `app/test/e2e/mock-server.ts`
|
||||
- WDIO config: `app/test/wdio.conf.ts` (auto-detects platform)
|
||||
|
||||
- **Build + run**:
|
||||
|
||||
```bash
|
||||
# Build app + stage core sidecar (detects macOS vs Linux automatically)
|
||||
pnpm test:e2e:build
|
||||
|
||||
# Run one spec
|
||||
bash app/scripts/e2e-run-spec.sh test/e2e/specs/smoke.spec.ts smoke
|
||||
|
||||
# Run all flow specs
|
||||
pnpm test:e2e:all:flows
|
||||
|
||||
# Docker on macOS (run Linux E2E locally)
|
||||
docker compose -f e2e/docker-compose.yml run --rm e2e
|
||||
```
|
||||
|
||||
- **Authoring rules**:
|
||||
- Ensure each spec is runnable in isolation.
|
||||
- Use helpers from `element-helpers.ts` — never use raw `XCUIElementType*` selectors in specs.
|
||||
- Use `clickNativeButton()`, `hasAppChrome()`, `waitForWebView()`, `clickToggle()` for cross-platform element interaction.
|
||||
- Assert both UI outcomes and backend/mock effects when relevant.
|
||||
- Add failure diagnostics (request logs, `dumpAccessibilityTree()`) for faster debugging by agents.
|
||||
|
||||
### Deterministic core-sidecar reset
|
||||
|
||||
By default, `app/scripts/e2e-run-spec.sh` creates and cleans a temp `OPENHUMAN_WORKSPACE`
|
||||
automatically when the variable is not provided.
|
||||
|
||||
If you need a fixed workspace for debugging, provide one explicitly:
|
||||
|
||||
```bash
|
||||
export OPENHUMAN_WORKSPACE="$(mktemp -d)"
|
||||
pnpm test:e2e:build
|
||||
bash app/scripts/e2e-run-spec.sh test/e2e/specs/smoke.spec.ts smoke
|
||||
rm -rf "$OPENHUMAN_WORKSPACE"
|
||||
```
|
||||
|
||||
- `OPENHUMAN_WORKSPACE` redirects core config + workspace storage away from `~/.openhuman`.
|
||||
- Default reset strategy:
|
||||
- Rebuild/stage sidecar once per E2E run (`pnpm test:e2e:build`).
|
||||
- Isolate state per test case with a fresh temp workspace (default behavior in `e2e-run-spec.sh`).
|
||||
|
||||
### Rust tests with mock backend
|
||||
|
||||
Use the shared mock backend runner so Rust unit/integration tests get deterministic API behavior:
|
||||
### Rust tests
|
||||
|
||||
```bash
|
||||
pnpm test:rust
|
||||
# or targeted
|
||||
bash scripts/test-rust-with-mock.sh --test json_rpc_e2e
|
||||
```
|
||||
|
||||
Example per-test-case pattern inside a harness script:
|
||||
|
||||
```bash
|
||||
run_case() {
|
||||
export OPENHUMAN_WORKSPACE="$(mktemp -d)"
|
||||
bash app/scripts/e2e-run-spec.sh "$1" "$2"
|
||||
rm -rf "$OPENHUMAN_WORKSPACE"
|
||||
}
|
||||
```
|
||||
|
||||
- **Rust test file naming**: when extracting Rust tests out of an implementation file, prefer a sibling `*_test.rs` file wired in with `#[cfg(test)] #[path = "..._test.rs"] mod tests;`. Do not create ad hoc `_test/` or `_tests/` directories for single-module Rust tests unless a broader multi-file test fixture truly requires a directory.
|
||||
|
||||
### Test authoring checklist
|
||||
|
||||
- Add/update unit tests for logic changes before stacking additional features.
|
||||
- Add/update E2E coverage for user-visible flows and cross-process integration behavior.
|
||||
- Keep new tests independent, deterministic, and debuggable from logs alone.
|
||||
- When touching core/sidecar behavior, validate both:
|
||||
- `pnpm test:unit`
|
||||
- targeted E2E spec(s) via `app/scripts/e2e-run-spec.sh`
|
||||
|
||||
---
|
||||
|
||||
## Frontend (`app/src/`)
|
||||
|
||||
### Provider chain (`app/src/App.tsx`)
|
||||
**Provider chain** (`App.tsx`): `Sentry.ErrorBoundary` → `Redux Provider` → `PersistGate` → `BootCheckGate` → `CoreStateProvider` → `SocketProvider` → `ChatRuntimeProvider` → `HashRouter` → `CommandProvider` → `ServiceBlockingGate` → `AppShell`.
|
||||
|
||||
Order matters for auth and realtime:
|
||||
No `UserProvider`/`AIProvider`/`SkillProvider` — auth lives in `CoreStateProvider` via `fetchCoreAppSnapshot()` RPC.
|
||||
|
||||
`Sentry.ErrorBoundary` → `Redux Provider` → `PersistGate` (with `PersistRehydrationScreen`) → `BootCheckGate` → **`CoreStateProvider`** → **`SocketProvider`** → **`ChatRuntimeProvider`** → `HashRouter` → `CommandProvider` → `ServiceBlockingGate` → `AppShell` (`AppRoutes` + `BottomTabBar` + `AppWalkthrough` + `MascotFrameProducer`).
|
||||
**State** (`store/`): Redux Toolkit slices — `accounts`, `channelConnections`, `chatRuntime`, `coreMode`, `deepLinkAuth`, `mascot`, `notification`, `providerSurface`, `socket`, `thread`. Prefer Redux over ad-hoc `localStorage`.
|
||||
|
||||
`CoreStateProvider` owns auth: session tokens are NOT in redux-persist; they live in the in-process core and are fetched via `fetchCoreAppSnapshot()` RPC. There is no `UserProvider`, `AIProvider`, `SkillProvider`, or `TelegramProvider`.
|
||||
**Services** (`services/`): `apiClient`, `socketService`, `coreRpcClient`, `coreCommandClient`, `chatService`, `analytics`, `notificationService`, `webviewAccountService`, `daemonHealthService`, plus domain `api/*` clients. Always use `invoke('core_rpc_relay', ...)` for core RPC.
|
||||
|
||||
### State (`app/src/store/`)
|
||||
**Routing** (`AppRoutes.tsx`, HashRouter): `/` (Welcome), `/onboarding/*`, `/home`, `/human`, `/intelligence`, `/skills`, `/chat`, `/channels`, `/invites`, `/notifications`, `/rewards`, `/settings/*`. No `/login`, `/mnemonic`, `/agents`, `/conversations`.
|
||||
|
||||
Redux Toolkit slices: `accounts`, `channelConnections`, `chatRuntime`, `coreMode`, `deepLinkAuth`, `mascot`, `notification`, `providerSurface`, `socket` (+ `socketSelectors`), `thread`. Plus `userScopedStorage` for per-user persistence keys. Prefer Redux (and redux-persist where configured) over ad hoc `localStorage` for app state. Documented exception: ephemeral UI state like upsell-banner dismiss flags use `localStorage` with the `openhuman:upsell:` prefix.
|
||||
|
||||
### Services (`app/src/services/`)
|
||||
|
||||
Singleton-style modules: `apiClient`, `socketService`, `coreRpcClient`, `coreCommandClient`, `chatService`, `analytics`, `notificationService`, `webviewAccountService`, `daemonHealthService`, `meetCallService`, `memorySyncService`, `bootCheckService`, `walletApi`, plus domain `api/*` clients. Always call the in-process core via `invoke('core_rpc_relay', ...)` — never raw `fetch()` (CORS preflight) or `callCoreRpc()` for service-status calls (socket may not be connected yet).
|
||||
|
||||
### MCP (`app/src/lib/mcp/`)
|
||||
|
||||
Transport, validation, and types for JSON-RPC-style messaging over Socket.io. Tooling for agents is driven by the **skills** catalog metadata + backend tool registry; see `agentToolRegistry.ts` and core RPC. (Skill execution itself moved out-of-process when the QuickJS runtime was removed.)
|
||||
|
||||
### Routing (`app/src/AppRoutes.tsx`, HashRouter)
|
||||
|
||||
`/` (Welcome, public), `/onboarding/*`, `/home`, `/human`, `/intelligence`, `/skills`, `/chat` (unified agent + connected web apps — replaces the old `/conversations` and `/accounts` routes), `/channels`, `/invites`, `/notifications`, `/rewards`, `/webhooks` → redirect to `/settings/webhooks-triggers`, `/settings/*`. Default `*` → `DefaultRedirect`. There is **no** `/login`, **no** `/mnemonic` (Recovery Phrase moved to Settings panel), **no** `/agents`, **no** `/conversations`.
|
||||
|
||||
### AI configuration
|
||||
|
||||
Bundled prompts live under **`src/openhuman/agent/prompts/`** at the **repository root** (also bundled via `app/src-tauri/tauri.conf.json` `resources`). Loaders under `app/src/lib/ai/` use `?raw` imports, optional remote fetch, and in Tauri **`ai_get_config` / `ai_refresh_config`** for packaged content.
|
||||
**AI config**: bundled prompts in `src/openhuman/agent/prompts/` (also via `tauri.conf.json` resources). Loaders in `app/src/lib/ai/` with `?raw` imports.
|
||||
|
||||
---
|
||||
|
||||
## Tauri shell (`app/src-tauri/`)
|
||||
|
||||
Thin desktop host. Top-level modules: `core_process`, `core_rpc`, `cdp`, `cef_preflight`, `cef_profile`, `dictation_hotkeys`, `file_logging`, `mascot_native_window`, `native_notifications`, `notification_settings`, `process_kill`, `process_recovery`, `screen_capture`, `window_state`, plus per-provider scanners (`discord_scanner`, `gmessages_scanner`, `imessage_scanner`, `meet_scanner`, `slack_scanner`, `telegram_scanner`, `whatsapp_scanner`), `meet_audio` / `meet_call` / `meet_video`, `fake_camera`, `webview_accounts`, `webview_apis`.
|
||||
Thin desktop host. Key modules: `core_process`, `core_rpc`, `cdp`, `cef_preflight`, `cef_profile`, `dictation_hotkeys`, `file_logging`, `mascot_native_window`, `screen_capture`, `window_state`, per-provider scanners (`discord_scanner`, `slack_scanner`, `telegram_scanner`, `whatsapp_scanner`, etc.), `meet_audio`/`meet_call`/`meet_video`, `fake_camera`, `webview_accounts`, `webview_apis`.
|
||||
|
||||
**Core lifecycle**: `core_process::CoreProcessHandle` spawns the in-process JSON-RPC server and authenticates inbound RPC with a hex bearer that the shell hands the embedded server in-memory via `run_server_embedded_with_ready(rpc_token: Some(_))` (no env-var crossing). Stale-listener policy (#1130): on conflict the handle probes `GET /`, decides if the listener is an OpenHuman core, then `kill_pid_term` → `kill_pid_force` with PID revalidation guarding against PID reuse. `restart_core_process` / `start_core_process` Tauri commands let the frontend cycle it for updates.
|
||||
IPC commands: `greet`, `write_ai_config_file`, `ai_get_config`, `ai_refresh_config`, `core_rpc_relay`, `core_rpc_token`, `start_core_process`, `restart_core_process`, window commands, `openhuman_*` daemon helpers.
|
||||
|
||||
Registered IPC commands (see [`gitbooks/developing/architecture/tauri-shell.md`](gitbooks/developing/architecture/tauri-shell.md)) include `greet`, `write_ai_config_file`, `ai_get_config`, `ai_refresh_config`, `core_rpc_relay`, `core_rpc_token`, `start_core_process`, `restart_core_process`, window commands, and `openhuman_*` daemon helpers.
|
||||
### CEF child webviews — no new JS injection
|
||||
|
||||
Deep link plugin is registered where supported; behavior is platform-specific (see platform notes below).
|
||||
Embedded provider webviews **must not** grow new JS injection. No new `.js` under `webview_accounts/`, no new `build_init_script`/`RUNTIME_JS` blocks, no CDP `Page.addScriptToEvaluateOnNewDocument`. New behavior lives in CEF handlers, CDP from scanner modules, or Rust-side IPC hooks. Legacy injection (gmail, linkedin, google-meet) is grandfathered but should shrink. Audit new Tauri plugins for `js_init_script` calls.
|
||||
|
||||
---
|
||||
|
||||
## Rust core (repo root `src/`)
|
||||
## Rust core (`src/`)
|
||||
|
||||
- **`src/openhuman/`** — Domain logic. Current domains: `about_app`, `accessibility`, `agent`, `app_state`, `approval`, `autocomplete`, `billing`, `channels`, `composio`, `config`, `context`, `cost`, `credentials`, `cron`, `doctor`, `embeddings`, `encryption`, `health`, `heartbeat`, `integrations`, `learning`, `local_ai`, `meet`, `meet_agent`, `memory`, `migration`, `node_runtime`, `notifications`, `overlay`, `people`, `prompt_injection`, `provider_surfaces`, `providers`, `redirect_links`, `referral`, `routing`, `scheduler_gate`, `screen_intelligence`, `security`, `service`, `skills`, `socket`, `subconscious`, `team`, `text_input`, `threads`, `tokenjuice`, `tool_timeout`, `tools`, `tree_summarizer`, `update`, `voice`, `wallet`, `webhooks`, `webview_accounts`, `webview_apis`, `webview_notifications`. RPC controllers in per-domain `rpc.rs`; use **`RpcOutcome<T>`** pattern (see "RPC Controller Pattern" below).
|
||||
- **`src/openhuman/` module layout**: **New** functionality must live in a **dedicated subdirectory** (e.g. `openhuman/my_domain/mod.rs` plus related files, or a new subfolder under an existing domain). Do **not** add new standalone `*.rs` files directly at `src/openhuman/` root (`dev_paths.rs` and `util.rs` are grandfathered).
|
||||
- **Tool ownership rule**: Tool implementations that belong to a domain/module must live in that owning module's `tools.rs` (and optional `tools/` submodules), not in `src/openhuman/tools/impl/`. Re-export those tool types from `src/openhuman/tools/mod.rs` so callers continue to import agent-callable tools through `crate::openhuman::tools::*`. Keep `src/openhuman/tools/impl/` only for genuinely cross-cutting tool families without a clear owning domain (for example filesystem, browser/computer control, and generic system/network utilities).
|
||||
- **Memory source identity rule**: Do not use per-item selector IDs (Notion page id, GitHub issue/PR id, Linear issue id, ClickUp task id, etc.) as the source tree / raw archive / Obsidian source-tag identity. Item IDs may stay in `metadata.source_id` when they are needed as document dedupe keys, but set `metadata.path_scope` (via `ingest_document_with_scope`) to the stable collection identity, usually `<provider>:<connection_id>` or a provider-native collection like `github.com/<owner>/<repo>`. The user-visible graph and `<workspace>/memory_tree/content/raw/<source_slug>/_source.md` must represent one logical selector/source, not one raw source per item.
|
||||
- **Controller schema contract**: Shared controller metadata types live in `src/core/types.rs` / `src/core/mod.rs` (`ControllerSchema`, `FieldSchema`, `TypeSchema`) and are consumed by adapters (RPC/CLI).
|
||||
- **Domain schema files**: For each domain, define controller schema metadata in a dedicated module inside the domain folder (example: `src/openhuman/cron/schemas.rs`) and export from the domain `mod.rs`.
|
||||
- **Controller-only exposure rule**: Expose domain functionality to **CLI and JSON-RPC through the controller registry** (`schemas.rs` + registered handlers wired into `src/core/all.rs`). Do **not** add domain-specific branches in `src/core/cli.rs` or `src/core/jsonrpc.rs`.
|
||||
- **Light `mod.rs` rule**: Keep domain `mod.rs` files light and export-focused. Put operational code in sibling files (`ops.rs`, `store.rs`, `schedule.rs`, `types.rs`, `bus.rs`). See the **Canonical module shape** table in [`CLAUDE.md`](CLAUDE.md) for the full per-file contract (it is the single source of truth for module structure).
|
||||
- **`src/core/`** — Transport only: Axum/HTTP, JSON-RPC envelope, CLI parsing, **dispatch** (`src/core/dispatch.rs`), auth, observability, event bus. **No** heavy business logic here. (Older docs that say `core_server` mean this directory; there is no `src/core_server/`.)
|
||||
- **Layering**: Implementation in `openhuman::<domain>/`, controllers in `openhuman::<domain>/rpc.rs`, routes/dispatch in `src/core/`.
|
||||
### Domain layout (`src/openhuman/`)
|
||||
|
||||
The previous QuickJS / `rquickjs` skills runtime has been removed. `src/openhuman/skills/` now contains metadata helpers only (`ops_create`, `ops_discover`, `ops_install`, `ops_parse`, `inject`, `schemas`, `types`) — its `mod.rs` and `types.rs` carry the marker comment "Legacy … retained after QuickJS runtime removal." Do not assume the runtime can execute a `.skill` package end-to-end; check what `ops_install` and the current agent tool path actually do before planning a feature that needs it.
|
||||
Domains: `about_app`, `accessibility`, `agent`, `app_state`, `approval`, `autocomplete`, `billing`, `channels`, `composio`, `config`, `context`, `cost`, `credentials`, `cron`, `doctor`, `embeddings`, `encryption`, `health`, `heartbeat`, `integrations`, `learning`, `local_ai`, `meet`, `meet_agent`, `memory`, `migration`, `node_runtime`, `notifications`, `overlay`, `people`, `prompt_injection`, `provider_surfaces`, `providers`, `redirect_links`, `referral`, `routing`, `scheduler_gate`, `screen_intelligence`, `security`, `service`, `skills`, `socket`, `subconscious`, `team`, `text_input`, `threads`, `tokenjuice`, `tool_timeout`, `tools`, `tree_summarizer`, `update`, `voice`, `wallet`, `webhooks`, `webview_accounts`, `webview_apis`, `webview_notifications`.
|
||||
|
||||
**Skills runtime removed**: QuickJS gone. `src/openhuman/skills/` is metadata-only now.
|
||||
|
||||
**Rules:**
|
||||
|
||||
- New functionality → dedicated subdirectory (`openhuman/<domain>/mod.rs` + siblings). No new root-level `*.rs` files.
|
||||
- **Tool ownership**: domain tools live in that domain's `tools.rs`, re-exported via `src/openhuman/tools/mod.rs`. Only cross-cutting families stay in `tools/impl/`.
|
||||
- **Memory source identity**: per-item IDs are dedupe keys only; set `metadata.path_scope` to stable collection scope.
|
||||
- **Controller-only exposure**: use the registry, not branches in `cli.rs`/`jsonrpc.rs`.
|
||||
|
||||
### Canonical module shape
|
||||
|
||||
| File | When | Role |
|
||||
| ------------ | ---------------------------- | --------------------------------------------------------------------------------------------- |
|
||||
| `mod.rs` | always | Export-focused only: `mod`/`pub mod` + `pub use` + controller schema pair. No business logic. |
|
||||
| `types.rs` | domain has types | Serde domain types. |
|
||||
| `store.rs` | domain persists | Persistence layer. |
|
||||
| `ops.rs` | domain has logic | Business logic + handlers returning `RpcOutcome<T>`. |
|
||||
| `schemas.rs` | RPC-facing | Controller schemas + `handle_*` fns delegating to `ops.rs`. |
|
||||
| `tools.rs` | domain owns agent tools | Tool implementations. |
|
||||
| `bus.rs` | domain has event subscribers | `EventHandler` impls. |
|
||||
| tests | new/changed behavior | Inline `#[cfg(test)] mod tests` or sibling `*_tests.rs`. |
|
||||
|
||||
### Controller migration checklist
|
||||
|
||||
- `src/openhuman/<domain>/mod.rs`: keep export-focused, add `mod schemas;` and re-export:
|
||||
- `all_controller_schemas as all_<domain>_controller_schemas`
|
||||
- `all_registered_controllers as all_<domain>_registered_controllers`
|
||||
- `src/openhuman/<domain>/schemas.rs` must define:
|
||||
- `schemas(function: &str) -> ControllerSchema`
|
||||
- `all_controller_schemas() -> Vec<ControllerSchema>`
|
||||
- `all_registered_controllers() -> Vec<RegisteredController>`
|
||||
- domain handler fns `fn handle_*(_: Map<String, Value>) -> ControllerFuture`
|
||||
- Handlers should delegate to existing domain `rpc.rs` functions during migration.
|
||||
- Wire domain exports into `src/core/all.rs` for both declared schemas and registered handlers.
|
||||
- Keep adapters generic: do not add domain-specific logic to `src/core/cli.rs` or `src/core/jsonrpc.rs`.
|
||||
- Remove migrated method branches from `src/rpc/dispatch.rs` once registry coverage is in place.
|
||||
1. `mod.rs`: add `mod schemas;`, re-export `all_controller_schemas`/`all_registered_controllers`.
|
||||
2. `schemas.rs`: define schemas, handlers delegating to `ops.rs`.
|
||||
3. Wire into `src/core/all.rs`. Remove from `src/core/dispatch.rs`.
|
||||
|
||||
### `src/core/` — transport only
|
||||
|
||||
Modules: `all`, `auth`, `cli`, `dispatch`, `event_bus/`, `jsonrpc`, `logging`, `observability`, `types`, etc. No business logic here.
|
||||
|
||||
### Event bus (`src/core/event_bus/`)
|
||||
|
||||
A typed pub/sub event bus for **decoupled cross-module communication** plus a **native, in-process typed request/response** surface. Both are singletons — one instance each for the whole application. Do **not** construct `EventBus` or `NativeRegistry` directly; use the module-level functions.
|
||||
Typed pub/sub + native request/response. Both singletons — use module-level functions.
|
||||
|
||||
**When to use which surface:**
|
||||
- **Broadcast** (`publish_global`/`subscribe_global`): fire-and-forget, many subscribers.
|
||||
- **Native request/response** (`register_native_global`/`request_native_global`): one-to-one typed dispatch, zero serialization, internal-only.
|
||||
|
||||
- **Broadcast events** (`publish_global` / `subscribe_global`) — fire-and-forget notification. One publisher, many subscribers, no return value. Use when a module needs to _announce_ something happened and other modules may react independently.
|
||||
- **Native request/response** (`register_native_global` / `request_native_global`) — one-to-one typed Rust dispatch keyed by a method string. **Zero serialization**: trait objects (`Arc<dyn Provider>`), streaming channels (`mpsc::Sender<T>`), oneshot senders, and anything else `Send + 'static` all pass through unchanged. Use when a module needs a typed return value from another module in-process. This is **internal-only** — anything that needs to be callable over JSON-RPC should register against `src/core/all.rs` instead.
|
||||
Core types: `DomainEvent` (events.rs), `EventBus` (bus.rs), `NativeRegistry` (native_request.rs), `EventHandler`/`SubscriptionHandle` (subscriber.rs).
|
||||
|
||||
**Core types** (all in `src/core/event_bus/`):
|
||||
Domains: `agent`, `memory`, `channel`, `cron`, `skill`, `tool`, `webhook`, `system`.
|
||||
|
||||
| Type | File | Purpose |
|
||||
|------|------|---------|
|
||||
| `DomainEvent` | `events.rs` | `#[non_exhaustive]` enum — all cross-module events live here, grouped by domain |
|
||||
| `EventBus` | `bus.rs` | Singleton backed by `tokio::sync::broadcast`. Construction is `pub(crate)` — tests only |
|
||||
| `NativeRegistry` / `NativeRequestError` | `native_request.rs` | In-process typed request/response registry keyed by method name. Rust types only — passes trait objects, `mpsc::Sender`, and `oneshot::Sender` through without serialization |
|
||||
| `EventHandler` | `subscriber.rs` | Async trait with optional `domains()` filter for selective subscription |
|
||||
| `SubscriptionHandle` | `subscriber.rs` | RAII handle — subscriber task is cancelled on drop |
|
||||
| `TracingSubscriber` | `tracing.rs` | Built-in debug logger for all events (registered at startup) |
|
||||
Each domain owns `bus.rs` with handlers. Convention: `<Purpose>Subscriber`, `name()` → `"<domain>::<purpose>"`.
|
||||
|
||||
**Singleton API** (all modules use these — never hold or pass `EventBus` / `NativeRegistry` instances):
|
||||
**Adding events:** add to `DomainEvent`, extend `domain()` match, create `<domain>/bus.rs`, register at startup, publish via `publish_global`.
|
||||
|
||||
| Function | Purpose |
|
||||
|----------|---------|
|
||||
| `event_bus::init_global(capacity)` | Initialize both singletons (broadcast bus + native registry) at startup (once) |
|
||||
| `event_bus::publish_global(event)` | Publish a broadcast event from anywhere (no-op if not yet initialized) |
|
||||
| `event_bus::subscribe_global(handler)` | Subscribe to broadcast events from anywhere (returns `None` if not yet initialized) |
|
||||
| `event_bus::register_native_global(method, handler)` | Register a typed native request handler for a method name — called at startup by each domain's `bus.rs` |
|
||||
| `event_bus::request_native_global(method, req)` | Dispatch a typed native request to the registered handler — zero serialization |
|
||||
| `event_bus::global()` / `event_bus::native_registry()` | Get the underlying singleton for advanced use |
|
||||
|
||||
**Domains:** `agent`, `memory`, `channel`, `cron`, `skill`, `tool`, `webhook`, `system`. See `events.rs` for the full variant list — events carry rich payloads so subscribers have everything they need.
|
||||
|
||||
**Domain subscriber files** — each domain owns its `bus.rs` with `EventHandler` impls:
|
||||
- `cron/bus.rs` — `CronDeliverySubscriber` (delivers job output to channels)
|
||||
- `webhooks/bus.rs` — `WebhookRequestSubscriber` (routes incoming requests to skills, emits responses via socket)
|
||||
- `channels/bus.rs` — `ChannelInboundSubscriber` (runs agent loop for inbound socket messages)
|
||||
- `skills/bus.rs` — stub for future subscribers
|
||||
|
||||
**Adding events for a new domain:**
|
||||
|
||||
1. Add variants to `DomainEvent` in `events.rs` (prefix with domain name, e.g. `BillingInvoiceCreated { ... }`).
|
||||
2. Add the domain string to the `domain()` match arm.
|
||||
3. Create a `bus.rs` file **inside your domain module** (e.g. `src/openhuman/billing/bus.rs`) for subscriber implementations — each domain owns its handlers.
|
||||
4. Register subscribers in startup (e.g. `channels/runtime/startup.rs`) via the singleton.
|
||||
5. Publish events with `event_bus::publish_global(DomainEvent::YourEvent { ... })`.
|
||||
|
||||
**Example — publishing:**
|
||||
```rust
|
||||
use crate::core::event_bus::{publish_global, DomainEvent};
|
||||
|
||||
publish_global(DomainEvent::CronDeliveryRequested {
|
||||
job_id: job.id.clone(),
|
||||
channel: "telegram".into(),
|
||||
target: "chat-123".into(),
|
||||
output: "Job completed".into(),
|
||||
});
|
||||
```
|
||||
|
||||
**Example — subscribing (trait-based, in `<domain>/bus.rs`):**
|
||||
```rust
|
||||
use crate::core::event_bus::{DomainEvent, EventHandler};
|
||||
use async_trait::async_trait;
|
||||
|
||||
pub struct MyDomainSubscriber { /* dependencies */ }
|
||||
|
||||
#[async_trait]
|
||||
impl EventHandler for MyDomainSubscriber {
|
||||
fn name(&self) -> &str { "my_domain::handler" }
|
||||
fn domains(&self) -> Option<&[&str]> { Some(&["cron"]) } // filter by domain
|
||||
async fn handle(&self, event: &DomainEvent) {
|
||||
if let DomainEvent::CronJobCompleted { job_id, success } = event {
|
||||
// react to the event
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Convention:** Name the handler struct `<Purpose>Subscriber` (e.g. `CronDeliverySubscriber`) and the `name()` return value `"<domain>::<purpose>"` for grep-friendly tracing output.
|
||||
|
||||
**Adding a native request handler for a new domain:**
|
||||
|
||||
1. Define the **request and response types** in the domain (e.g. `src/openhuman/billing/bus.rs`). Use owned fields, `Arc`s, and channels — not borrows. Types only need `Send + 'static`, not `Serialize`.
|
||||
2. Register the handler at startup from the same `bus.rs`, keyed by a stable method name prefixed with the domain (e.g. `"billing.charge_invoice"`).
|
||||
3. Callers import the request/response types from the domain's public surface and dispatch via `request_native_global`.
|
||||
4. Method name convention: `"<domain>.<verb>"` — same naming scheme as JSON-RPC method roots for consistency, but these are **not** exposed over JSON-RPC.
|
||||
|
||||
**Example — native request (typed request/response, in `<domain>/bus.rs`):**
|
||||
```rust
|
||||
use crate::core::event_bus::{register_native_global, request_native_global};
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
// Request carries non-serializable state directly — trait objects and
|
||||
// streaming channels all pass through unchanged.
|
||||
pub struct BillingChargeRequest {
|
||||
pub provider: Arc<dyn BillingProvider>,
|
||||
pub amount_cents: u64,
|
||||
pub progress_tx: Option<mpsc::Sender<String>>,
|
||||
}
|
||||
pub struct BillingChargeResponse {
|
||||
pub charge_id: String,
|
||||
}
|
||||
|
||||
// At startup:
|
||||
pub async fn register_billing_handlers() {
|
||||
register_native_global::<BillingChargeRequest, BillingChargeResponse, _, _>(
|
||||
"billing.charge",
|
||||
|req| async move {
|
||||
let id = req.provider.charge(req.amount_cents).await
|
||||
.map_err(|e| e.to_string())?;
|
||||
Ok(BillingChargeResponse { charge_id: id })
|
||||
},
|
||||
).await;
|
||||
}
|
||||
|
||||
// From another module:
|
||||
let resp: BillingChargeResponse = request_native_global(
|
||||
"billing.charge",
|
||||
BillingChargeRequest { provider, amount_cents: 500, progress_tx: None },
|
||||
).await?;
|
||||
```
|
||||
|
||||
**Tests:** override production handlers by calling `register_native_global` again for the same method before exercising the code under test — the most recent registration wins. For full isolation, construct a fresh `NativeRegistry` directly via `NativeRegistry::new()` and use its `register` / `request` methods.
|
||||
**Adding native handlers:** define req/resp types (`Send + 'static`, not `Serialize`), register at startup keyed by `"<domain>.<verb>"`, dispatch via `request_native_global`.
|
||||
|
||||
---
|
||||
|
||||
## App theme & design system
|
||||
## Design & patterns
|
||||
|
||||
**Design intent**: Premium, calm visual language — ocean primary (`#4A83DD`), sage / amber / coral semantic colors, Inter + Cabinet Grotesk + JetBrains Mono, Tailwind with custom radii/spacing/shadows. Implementation tokens live in [`app/tailwind.config.js`](app/tailwind.config.js).
|
||||
**Visual**: ocean primary `#4A83DD`, sage/amber/coral semantics, Inter + Cabinet Grotesk + JetBrains Mono. Tokens in [`app/tailwind.config.js`](app/tailwind.config.js).
|
||||
|
||||
## Desktop shell (Tauri) vs application code
|
||||
**Key rules:**
|
||||
|
||||
In the parent **OpenHuman** desktop app, **Tauri / Rust is a delivery vehicle**: windowing, process lifecycle, IPC to the core sidecar, and other host concerns. **Keep as much UI behavior and product logic as practical in TypeScript/React** (`app/`). Avoid growing Rust in the shell for flows that belong in the web layer unless there is a hard platform or security reason.
|
||||
- File size: prefer ≤ ~500 lines.
|
||||
- **No dynamic imports** in production `app/src` — static `import`/`import type` only. Guard heavy paths with try/catch. Exceptions: test files, `.d.ts`, config files.
|
||||
- **i18n**: all UI text through `useT()` from `app/src/lib/i18n/I18nContext`. Add key to `en.ts` **and real translations to all locale files** (`ar`, `bn`, `de`, `es`, `fr`, `hi`, `id`, `it`, `ko`, `pl`, `pt`, `ru`, `zh-CN`). CI enforces parity (`pnpm i18n:check`) and detects English placeholders (`pnpm i18n:english:check`).
|
||||
- **Dual socket sync**: keep `socketService`/MCP transport aligned with core socket behavior.
|
||||
- **Tauri guard**: use `isTauri()` or wrap `invoke(...)` in try/catch — never check `window.__TAURI__` directly.
|
||||
|
||||
---
|
||||
|
||||
## Debug logging (must follow)
|
||||
|
||||
- Default to **verbose diagnostics** on new/changed flows.
|
||||
- Log entry/exit, branches, external calls, retries/timeouts, state transitions, errors.
|
||||
- Stable grep-friendly prefixes (`[domain]`, `[rpc]`), correlation fields (request IDs, method names).
|
||||
- Rust: `log`/`tracing` at `debug`/`trace`. App: namespaced `debug`.
|
||||
- **Never** log secrets or full PII.
|
||||
- Changes lacking logging are incomplete.
|
||||
|
||||
---
|
||||
|
||||
## Feature design workflow
|
||||
|
||||
Specify → prove in Rust → prove over RPC → surface in UI → test.
|
||||
|
||||
1. **Specify** — ground in existing domains, controller patterns, JSON-RPC naming (`openhuman.<namespace>_<function>`).
|
||||
2. **Implement in Rust** — domain logic + unit tests.
|
||||
3. **JSON-RPC E2E** — extend `tests/json_rpc_e2e.rs` / `scripts/test-rust-with-mock.sh`.
|
||||
4. **UI** — React + `core_rpc_relay`/`coreRpcClient`. Keep rules in core.
|
||||
5. **App unit tests** — Vitest.
|
||||
6. **App E2E** — desktop specs.
|
||||
|
||||
Update `src/openhuman/about_app/` when adding/removing/renaming user-facing features. Define E2E scenarios up front covering happy paths, failures, auth gates.
|
||||
|
||||
---
|
||||
|
||||
## Git workflow
|
||||
|
||||
- **Never write code on `main`.** Before making any code changes, fork a new branch off the latest `main` (`git fetch upstream && git checkout -b <branch> upstream/main`). All work happens on that feature branch; `main` stays clean and only advances via merged PRs.
|
||||
- **GitHub issues on upstream** — File and track issues on **[tinyhumansai/openhuman](https://github.com/tinyhumansai/openhuman/)** ([Issues](https://github.com/tinyhumansai/openhuman/issues)), not only a fork’s tracker, unless the workflow explicitly says otherwise.
|
||||
- **GitHub issue templates** — Use **[`.github/ISSUE_TEMPLATE/feature.md`](.github/ISSUE_TEMPLATE/feature.md)** for new features and **[`.github/ISSUE_TEMPLATE/bug.md`](.github/ISSUE_TEMPLATE/bug.md)** for bugs; keep the same section structure and fill every required part. AI-authored issues should follow those templates verbatim.
|
||||
- **Open pull requests on upstream** — Always create PRs against **[tinyhumansai/openhuman](https://github.com/tinyhumansai/openhuman)** ([pull requests](https://github.com/tinyhumansai/openhuman/pulls)), not only a fork’s default remote, unless the workflow explicitly says otherwise.
|
||||
- **Public repo**; push to your working branch; PRs target **`main`**.
|
||||
- **Agent branch rule** — If an agent starts work while checked out on `main`, it should create its own descriptive working branch before committing or pushing. Do not leave agent-authored commits on local `main`; move the pending work onto the new branch and ship from there.
|
||||
- Use [`.github/PULL_REQUEST_TEMPLATE.md`](.github/PULL_REQUEST_TEMPLATE.md); AI-generated PR text should follow its sections and checklist.
|
||||
Contribute via your fork. Recommended remotes:
|
||||
|
||||
---
|
||||
```text
|
||||
origin git@github.com:<your-username>/openhuman.git (push here)
|
||||
upstream git@github.com:tinyhumansai/openhuman.git (fetch-only)
|
||||
```
|
||||
|
||||
## Coding philosophy
|
||||
|
||||
- **Unix-style modules**: Prefer **individual modules** with a **single, sharp responsibility**—each should do one thing really well. Compose behavior through small, well-named units and clear boundaries instead of monolithic code.
|
||||
- **Tests before the next layer**: Ship **enough unit tests and coverage** for the behavior you are adding or changing **before** building additional features on top of it. Treat untested code as incomplete; do not accumulate depth on a shaky base.
|
||||
- **Documentation with code**: New or changed behavior must ship with matching documentation. At minimum, add concise rustdoc / code comments where the flow is not obvious, and update `AGENTS.md`, architecture docs, or feature docs when repository rules or user-visible behavior change.
|
||||
|
||||
---
|
||||
|
||||
## Debug logging rule (must follow)
|
||||
|
||||
- **Default to verbose diagnostics on new/changed flows**: Add substantial, development-oriented logs while implementing features or fixes so issues are easy to trace end-to-end.
|
||||
- **Log critical checkpoints**: Include logs at entry/exit points, branch decisions, external calls, retries/timeouts, state transitions, and error handling paths.
|
||||
- **Use structured, grep-friendly context**: Prefer stable prefixes (for example `[domain]`, `[rpc]`, `[ui-flow]`) and include correlation fields such as request IDs, method names, and entity IDs when available.
|
||||
- **Platform conventions**: In Rust, use `log` / `tracing` at `debug` or `trace`; in `app/`, use namespaced `debug` logs and dev-only detail as needed.
|
||||
- **Keep logs safe**: Never log secrets or sensitive payloads (API keys, JWTs, credentials, full PII). Redact or omit sensitive fields.
|
||||
- **Treat debuggability as a deliverable**: Changes lacking sufficient logging for diagnosis are incomplete and should be updated before handoff.
|
||||
|
||||
---
|
||||
|
||||
## Feature design workflow (new capabilities)
|
||||
|
||||
Follow this order so behavior is **specified**, **proven in Rust**, **proven over RPC**, then **surfaced in the UI** with matching tests.
|
||||
|
||||
1. **Specify against the current codebase** — Ground the design in **existing** domains, controller/registry patterns, and JSON-RPC naming (`openhuman.<namespace>_<function>`). Reuse or extend documented flows in [`gitbooks/developing/architecture.md`](gitbooks/developing/architecture.md) and sibling guides; avoid parallel architectures.
|
||||
2. **Implement in Rust** — Add domain logic under `src/openhuman/<domain>/`, wire **schemas + registered handlers** into the shared registry, and land **unit tests** in the crate (`cargo test -p openhuman`, focused modules) until the feature is correct in isolation.
|
||||
3. **JSON-RPC E2E** — Add or extend **integration-style tests** that call the real HTTP JSON-RPC surface (e.g. [`tests/json_rpc_e2e.rs`](tests/json_rpc_e2e.rs), mock backend / [`scripts/test-rust-with-mock.sh`](scripts/test-rust-with-mock.sh) as appropriate) so methods, params, and outcomes match what the UI will call.
|
||||
4. **UI in the Tauri app** — Build **React** screens, state, and **`core_rpc_relay` / `coreRpcClient`** usage in `app/`; keep **business rules** in the core, not duplicated in the shell.
|
||||
5. **App unit tests** — Cover components, hooks, and clients with **Vitest** (`pnpm test` / `pnpm test:unit` in `app/`).
|
||||
6. **App E2E** — Add **desktop E2E** specs where the feature is user-visible (`pnpm test:e2e*`, isolated workspace — see [Testing Guide (Unit + E2E)](#testing-guide-unit--e2e)) so the full stack (UI → Tauri → sidecar) behaves as intended.
|
||||
|
||||
**Capability catalog** — When a change adds, removes, renames, relocates, or materially changes a user-facing feature, update **`src/openhuman/about_app/`** in the same work so the runtime capability catalog remains the source of truth for what the app can do.
|
||||
|
||||
**Debug logging (throughout)** — Add **lots of development-oriented logging** as you build, not as an afterthought. In **Rust**, use `log` / `tracing` at **`debug`** or **`trace`** on RPC entry and exit, error paths, state transitions, and any branch that is hard to infer from tests alone. In **`app/`**, follow existing patterns (e.g. the **`debug`** npm package with a **namespace** per area) plus **dev-only** detail where useful. Prefer **grep-friendly prefixes** (`[feature]`, domain name, or JSON-RPC method) so terminal output from **sidecar**, **Tauri**, and **WebView** can be correlated during `pnpm dev` / `tauri dev`. **Never** log secrets, raw JWTs, API keys, or full PII—redact or omit.
|
||||
|
||||
**Planning rule:** When scoping a feature, define the **E2E scenarios (core RPC + app)** up front. Those scenarios should **cover the full intended scope**—happy paths, failure modes, auth or policy gates, and regressions you care about. If a scenario is not testable end-to-end, the spec is incomplete or the cut is too large; split or add harness support first.
|
||||
|
||||
---
|
||||
|
||||
## Key patterns (concise)
|
||||
|
||||
- **Debug logging**: Ship **heavy `debug`/`trace` (Rust)** and **namespaced `debug` / dev logs (`app/`)** on new flows so sidecar + WebView output is easy to grep; see [Feature design workflow](#feature-design-workflow-new-capabilities). Never log secrets or raw tokens.
|
||||
- **`src/openhuman/`**: New features go in a **folder/module**, not new root-level `src/openhuman/*.rs` files (see Rust core section).
|
||||
- **File size**: Prefer ≤ ~500 lines per source file; split modules when growing.
|
||||
- **Pre-merge checks** (when touching code): Prettier, ESLint, `tsc --noEmit` in `app/`; `cargo fmt` + `cargo check` for changed Rust (`Cargo.toml` at root and/or `app/src-tauri/Cargo.toml` as appropriate).
|
||||
- **No dynamic imports** in production **`app/src`** code — use **static** `import` / `import type` at the top of the module. Do **not** use `import()` (async dynamic import), `React.lazy(() => import(...))`, or `await import('…')` to load app modules, Tauri APIs, or RPC clients. **Why:** predictable chunk graph, simpler static analysis, fewer surprises in Tauri + Vite, and easier code review. **If a module must not run at load time** (e.g. heavy optional path), use a static import and **guard the call site** with `try/catch` or an explicit runtime check instead of deferring module load via dynamic import. **Exceptions:** Vitest harness patterns (`vi.importActual`, dynamic imports **only** inside `*.test.ts` / `__tests__` / `test/setup.ts` when required by the runner); ambient `typeof import('…')` in `.d.ts`; config files (e.g. `tailwind.config.js` JSDoc).- **Type-only imports**: `import type` where appropriate.
|
||||
- **Dual socket / tool sync**: If you change realtime protocol, keep **frontend** (`socketService` / MCP transport) and **core** socket behavior aligned (see [`gitbooks/developing/architecture.md`](gitbooks/developing/architecture.md) dual-socket section).
|
||||
- **i18n for all UI text**: Every user-visible string in `app/src/**` (headings, labels, button text, placeholders, status chips, toasts, dialog copy, `aria-label`, etc.) must go through `useT()` from `app/src/lib/i18n/I18nContext`. Hard-coded literals in JSX or `label=`/`placeholder=`/`aria-label=` props are not allowed. Add the new key to [`app/src/lib/i18n/en.ts`](app/src/lib/i18n/en.ts) in the same PR — other locales fall back to English. **Exceptions:** developer-only debug logs, code identifiers, and non-display data (URLs, slugs, technical sentinel values).
|
||||
- **i18n locale files — update ALL locales**: Each locale is a **single flat file** at `app/src/lib/i18n/<locale>.ts` (`en.ts` is the source of truth; the chunked `chunks/<locale>-N.ts` layout was retired). When adding or changing keys in `en.ts`, you **must also** add the same key to every non-English locale file (use the English value as a placeholder — translators fill in later). CI enforces parity via `pnpm i18n:check`; a missing or extra key in any locale will fail the i18n coverage gate. Locales: `ar`, `bn`, `de`, `es`, `fr`, `hi`, `id`, `it`, `ko`, `pl`, `pt`, `ru`, `zh-CN`.
|
||||
- **Never write code on `main`.** Branch off `upstream/main` for all work.
|
||||
- Issues and PRs on upstream `tinyhumansai/openhuman`.
|
||||
- Push to `origin` (fork), never `upstream`. PRs with `--head <your-username>:<branch>`.
|
||||
- Use issue/PR templates verbatim.
|
||||
- On push blockers: fix your own hook failures; bypass with `--no-verify` only for unrelated pre-existing breakage (call out in PR body).
|
||||
|
||||
---
|
||||
|
||||
## Platform notes
|
||||
|
||||
- **macOS deep links**: Often require a built **`.app`** bundle; not only `tauri dev`.
|
||||
- **`window.__TAURI__`**: Not assumed at module load; use `isTauri()` (from `app/src/services/webviewAccountService.ts`) or wrap `invoke(...)` in `try/catch`.
|
||||
- **Core is in-process**: `core_rpc` reaches `http://127.0.0.1:<port>/rpc` (default port `7788`) authenticated with a per-launch hex bearer. The desktop shell hands the bearer to the embedded server in-memory (no `OPENHUMAN_CORE_TOKEN` on the process env); docker / cloud / VPS operators still supply the bearer via `OPENHUMAN_CORE_TOKEN` (env-as-config). `scripts/stage-core-sidecar.mjs` no longer exists; `pnpm core:stage` is a no-op echo (sidecar removed in PR #1061). For standalone debugging: `./target/debug/openhuman-core serve` writes its token to `{workspace}/core.token` (default `~/.openhuman-staging/core.token` under `OPENHUMAN_APP_ENV=staging`); public endpoints `GET /health`, `GET /schema`, `GET /events` need no auth.
|
||||
- **Vendored CEF-aware `tauri-cli`**: only the vendored CLI at `app/src-tauri/vendor/tauri-cef/crates/tauri-cli` bundles Chromium correctly. Stock `@tauri-apps/cli` produces broken bundles. Reinstall: `cargo install --locked --path app/src-tauri/vendor/tauri-cef/crates/tauri-cli`.
|
||||
- **macOS deep links**: require built `.app` bundle, not just `tauri dev`.
|
||||
- **Windows deep links**: `openhuman://` registered via `tauri-plugin-deep-link::register_all`. Check in `app/src-tauri/src/deep_link_registration_check.rs`.
|
||||
- **Core standalone debugging**: `./target/debug/openhuman-core serve` (token at `{workspace}/core.token`). Public endpoints: `GET /health`, `GET /schema`, `GET /events`.
|
||||
|
||||
---
|
||||
|
||||
_Last aligned with monorepo layout (`app/` + root `src/`), in-process core (no sidecar), QuickJS removed, skills catalog on GitHub (`tinyhumansai/openhuman-skills`), and Tauri shell IPC as of `openhuman-app` v0.53.45 / repo `main`._
|
||||
## Coding philosophy
|
||||
|
||||
---
|
||||
|
||||
## Cursor Cloud specific instructions
|
||||
|
||||
### Environment overview
|
||||
|
||||
Two services run independently for development:
|
||||
|
||||
| Service | Start command | Port | Notes |
|
||||
|---------|--------------|------|-------|
|
||||
| **Vite dev server** | `pnpm dev` (from repo root) | 1420 | React frontend with HMR |
|
||||
| **Core JSON-RPC server** | `./target/debug/openhuman-core serve` | 7788 | Rust core, writes bearer token to `~/.openhuman-staging/core.token` |
|
||||
|
||||
The app connects to a **remote staging backend** at `https://staging-api.tinyhumans.ai` — there is no local backend to run.
|
||||
|
||||
### Running the core server standalone
|
||||
|
||||
The core generates a bearer token at startup written to `{workspace_dir}/core.token` (default `~/.openhuman-staging/core.token` when `OPENHUMAN_APP_ENV=staging`). Read that file for authenticated RPC calls:
|
||||
|
||||
```bash
|
||||
TOKEN=$(cat ~/.openhuman-staging/core.token)
|
||||
curl http://localhost:7788/rpc -X POST \
|
||||
-H 'Content-Type: application/json' \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-d '{"jsonrpc":"2.0","method":"core.ping","params":{},"id":1}'
|
||||
```
|
||||
|
||||
Public endpoints (no token needed): `GET /health`, `GET /schema`, `GET /events`.
|
||||
|
||||
### Linux build dependencies (non-obvious)
|
||||
|
||||
Compiling the Rust core on Linux requires these system packages beyond the basics:
|
||||
`libasound2-dev libxi-dev libxtst-dev libxdo-dev libudev-dev libssl-dev clang cmake pkg-config libstdc++-14-dev`
|
||||
|
||||
The `libstdc++-14-dev` package is needed because clang selects GCC 14 headers; without it, whisper-rs-sys fails with `fatal error: 'array' file not found`. A symlink may also be needed: `ln -sf /usr/lib/gcc/x86_64-linux-gnu/13/libstdc++.so /usr/lib/x86_64-linux-gnu/libstdc++.so`.
|
||||
|
||||
### Quick reference for common dev commands
|
||||
|
||||
All commands are documented in `CLAUDE.md` and `AGENTS.md` above. The most-used subset:
|
||||
|
||||
- **Lint**: `pnpm lint` (ESLint, 0 errors expected; warnings are acceptable)
|
||||
- **Typecheck**: `pnpm typecheck` (`tsc --noEmit`)
|
||||
- **Unit tests**: `pnpm test` (Vitest, runs 1000+ tests)
|
||||
- **Rust check**: `cargo check --manifest-path Cargo.toml`
|
||||
- **Rust tests**: `cargo test --lib` (5600+ tests)
|
||||
- **Format check**: `pnpm format:check`
|
||||
|
||||
### Running the Tauri desktop app on Linux cloud VMs
|
||||
|
||||
The full desktop app can be built and run on headless Linux VMs with:
|
||||
|
||||
```bash
|
||||
export CEF_PATH="$HOME/Library/Caches/tauri-cef"
|
||||
export LD_LIBRARY_PATH="$CEF_PATH/146.0.9/cef_linux_x86_64:$LD_LIBRARY_PATH"
|
||||
source scripts/load-dotenv.sh
|
||||
cargo tauri dev -- -- --no-sandbox
|
||||
```
|
||||
|
||||
Key requirements:
|
||||
- `--no-sandbox` is required because Chromium refuses to run as root without it.
|
||||
- `LD_LIBRARY_PATH` must include the CEF distribution directory so `libcef.so` is found at runtime.
|
||||
- The vendored CEF-aware `cargo-tauri` must be installed first via `bash scripts/ensure-tauri-cli.sh`.
|
||||
- First build downloads ~300MB CEF binary and compiles ~900 crates; subsequent builds are incremental.
|
||||
- GTK/cairo libraries are required: `libgtk-3-dev libwebkit2gtk-4.1-dev libsoup-3.0-dev libjavascriptcoregtk-4.1-dev libglib2.0-dev libcairo2-dev libpango1.0-dev libgdk-pixbuf-2.0-dev libatk1.0-dev libdbus-1-dev`.
|
||||
- WebGL errors in the log (`ContextResult::kFatalFailure: WebGL1/2 blocklisted`) are normal on GPU-less VMs and do not affect app functionality.
|
||||
|
||||
### Gotchas
|
||||
|
||||
- `pnpm install` may warn about ignored build scripts (`@sentry/cli`, `esbuild`, etc.). The esbuild binary is correctly installed via its native platform package despite the warning — Vite and Vitest work fine.
|
||||
- Git submodules (`app/src-tauri/vendor/tauri-cef`, `app/src-tauri/vendor/tauri-plugin-notification`) must be initialized for Tauri shell compilation. Run `git submodule update --init --recursive` if not already done.
|
||||
- `pnpm test:unit` does not exist at the root level; use `pnpm test` instead (which delegates to `vitest run` in the `app` workspace).
|
||||
- The Tauri shell `cargo check` requires GTK/desktop system libraries; without them, the pre-push hook's `pnpm rust:check` will fail. Use `--no-verify` on push if GTK libs are missing and the change is unrelated to the Tauri shell.
|
||||
|
||||
|
||||
<claude-mem-context>
|
||||
# Memory Context
|
||||
|
||||
# [openhuman] recent context, 2026-04-22 9:52am PDT
|
||||
|
||||
Legend: 🎯session 🔴bugfix 🟣feature 🔄refactor ✅change 🔵discovery ⚖️decision
|
||||
Format: ID TIME TYPE TITLE
|
||||
Fetch details: get_observations([IDs]) | Search: mem-search skill
|
||||
|
||||
Stats: 20 obs (8,333t read) | 593,112t work | 99% savings
|
||||
|
||||
### Apr 22, 2026
|
||||
2848 9:07a ✅ openhuman: All Three Review Branches Pushed to Fork Successfully
|
||||
2849 " 🔵 openhuman review-daemon-lifecycle: Two Post-Push Issues — Unstaged Prettier Changes + Missing tauri-cef Vendor
|
||||
2851 9:08a ✅ openhuman daemon lifecycle: Prettier Format Committed as Follow-Up
|
||||
2855 9:09a ✅ openhuman: All Three Review Branches Fully Pushed — PRs Ready to Open
|
||||
2857 9:10a 🔵 openhuman: GitHub Connector Cannot Create PRs to tinyhumansai/openhuman — 403 Forbidden
|
||||
2858 9:11a 🔵 openhuman webhooks-ingress: Session Stalled — Instruction Not Processed After 10+ Minutes
|
||||
2860 " 🔵 openhuman webhooks: WebhooksDebugPanel Architecture for E2E Smoke Spec
|
||||
2861 9:13a 🔵 openhuman webhooks-ingress: Full Spec Surface Mapped — RPC Log Strings + UI Navigation Path
|
||||
2866 9:15a 🟣 openhuman webhooks-ingress: webhooks-ingress-flow.spec.ts Written
|
||||
2869 9:18a ⚖️ openhuman Memory Refactor Plan: Trait Shape, L1 Pointer, and Missing Pieces
|
||||
2871 " 🔵 openhuman Memory Architecture: Auto-Inject Pattern Has 3 Separate Implementations
|
||||
2873 9:31a 🟣 openhuman: Draft PR Opened — Config Runtime Dir Refactor for Testability
|
||||
2874 9:32a 🟣 openhuman: 3 More Draft PRs Opened — Threads Schema, Daemon Lifecycle, Webhooks E2E
|
||||
2875 9:33a 🔵 openhuman Memory Namespace: 3 Auto-Inject Sites, Not 1
|
||||
2876 " ⚖️ openhuman Memory Refactor: Breaking Trait Change + Flag-Off + ToolDiscovery Hybrid
|
||||
2877 " ✅ Memory Namespace Refactor Plan Written to docs/plans/memory-namespace-refactor.md
|
||||
2879 9:34a 🔵 openhuman Memory Trait: 15 Impls, Not 14; MemoryRecalled Has No Live Emit Site
|
||||
2880 " 🔵 openhuman SQLite Schema: memory_docs Already Has namespace Column; Migration Scope Minimal
|
||||
2881 " 🔵 openhuman Memory Trait Current Signatures: No Namespace Param on Any Method
|
||||
2882 " 🔵 openhuman Eval Infra: Does Not Exist; Phase D Requires Bootstrap from Scratch
|
||||
|
||||
Access 593k tokens of past work via get_observations([IDs]) or mem-search skill.
|
||||
</claude-mem-context>
|
||||
- **Unix-style modules**: small, single-responsibility, composed through clear boundaries.
|
||||
- **Tests before the next layer**: untested code is incomplete.
|
||||
- **Docs with code**: update AGENTS.md or architecture docs when rules or behavior change.
|
||||
|
||||
@@ -1,395 +0,0 @@
|
||||
# OpenHuman
|
||||
|
||||
**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/architecture/frontend.md`](gitbooks/developing/architecture/frontend.md). Tauri shell: [`gitbooks/developing/architecture/tauri-shell.md`](gitbooks/developing/architecture/tauri-shell.md). Agent-harness tool surface: [`gitbooks/developing/architecture/agent-harness.md`](gitbooks/developing/architecture/agent-harness.md).
|
||||
|
||||
---
|
||||
|
||||
## Repository layout
|
||||
|
||||
| Path | Role |
|
||||
| --- | --- |
|
||||
| **`app/`** | pnpm workspace `openhuman-app` (v0.53.45): Vite + React (`app/src/`), Tauri desktop host (`app/src-tauri/`), Vitest tests |
|
||||
| **`src/`** (root) | Rust lib crate `openhuman` + `openhuman-core` CLI binary (`src/main.rs`) — `src/core/` (transport: Axum/HTTP, JSON-RPC, CLI), `src/openhuman/*` domains, event bus |
|
||||
| **`Cargo.toml`** (root) | Core crate; `cargo build --bin openhuman-core` produces the binary. Also defines `slack-backfill` and `gmail-backfill-3d` helper binaries in `src/bin/`. |
|
||||
| **`docs/`** | Remaining deep internals (memory pipeline excalidraws, sentry, etc.). Public contributor docs live in `gitbooks/developing/`. |
|
||||
|
||||
Commands assume the **repo root**; `pnpm dev` delegates to the `app` workspace. The root `package.json` is `openhuman-repo` (private) and enforces pnpm via the `packageManager` field.
|
||||
|
||||
---
|
||||
|
||||
## Runtime scope
|
||||
|
||||
- **Shipped product**: desktop — Windows, macOS, Linux.
|
||||
- **Tauri host** (`app/src-tauri`): desktop-only. No Android/iOS branches.
|
||||
- **Core runs in-process** inside the Tauri host as a tokio task — there is **no sidecar binary anymore** (removed in PR #1061). The lifecycle is owned by `core_process::CoreProcessHandle` in `app/src-tauri/src/core_process.rs`; on Cmd+Q the core dies with the GUI. Frontend RPC still goes over HTTP (`core_rpc_relay` + `core_rpc` client) to `http://127.0.0.1:<port>/rpc`, authenticated with a per-launch bearer the shell hands the embedded server in-memory via `run_server_embedded_with_ready(rpc_token: Some(_))`. The renderer reads the same bearer via the `core_rpc_token` Tauri command. `OPENHUMAN_CORE_TOKEN` is still honoured for CLI / docker / cloud env-as-config (operator-supplied) but is no longer set on the process env by the desktop shell. Set `OPENHUMAN_CORE_REUSE_EXISTING=1` to attach to an externally-started `openhuman-core` process (e.g. a debug harness).
|
||||
|
||||
**Where logic lives**
|
||||
- **Rust core**: business logic, execution, domains, RPC, persistence, CLI. Authoritative.
|
||||
- **Tauri + React (`app/`)**: UX, screens, navigation, bridging to the core. Presents and orchestrates only.
|
||||
|
||||
---
|
||||
|
||||
## iOS client (experimental)
|
||||
|
||||
The iOS client is an **in-progress, non-shipping** target in this repo. It does not ship a Rust core on-device; instead it connects to the desktop core via one of three transports selected by a `ConnectionProfile`.
|
||||
|
||||
**Transport strategies** (see `app/src/services/transport/`):
|
||||
- `LanHttpTransport` — direct HTTP to the desktop core on the same LAN.
|
||||
- `TunnelTransport` — socket.io relay through the backend; E2E encrypted with XChaCha20-Poly1305 over X25519 key agreement.
|
||||
- `CloudHttpTransport` — fallback via the cloud backend API.
|
||||
|
||||
**Key paths:**
|
||||
- PTT plugin: `packages/tauri-plugin-ptt/` (Swift + Rust, iOS-only).
|
||||
- iOS screens: `app/src/pages/ios/` and `app/src/components/ios/`.
|
||||
- Devices domain (Rust): `src/openhuman/devices/`.
|
||||
- Tunnel crypto (TS): `app/src/lib/tunnel/`.
|
||||
- iOS build entry: `pnpm tauri:ios:dev` — uses stock `@tauri-apps/cli@^2` via `npx`, **not** the vendored CEF CLI.
|
||||
- Setup guide: `docs/ios/SETUP.md`.
|
||||
|
||||
**Backend dependency:** `tinyhumansai/backend#709` (tunnel socket.io contract) must be merged and deployed for end-to-end pairing to work.
|
||||
|
||||
---
|
||||
|
||||
## Commands (from repo root)
|
||||
|
||||
```bash
|
||||
pnpm dev # Vite dev server only (app workspace)
|
||||
pnpm dev:app # Full Tauri desktop dev (CEF runtime, loads env via scripts/load-dotenv.sh)
|
||||
pnpm build # Production UI build
|
||||
pnpm typecheck # tsc --noEmit (app workspace, aliased to `compile`)
|
||||
pnpm compile # Same as typecheck
|
||||
pnpm lint # ESLint --cache
|
||||
pnpm format # Prettier write + cargo fmt
|
||||
pnpm format:check # Prettier check + cargo fmt --check
|
||||
|
||||
# Rust — core library + CLI
|
||||
cargo check --manifest-path Cargo.toml
|
||||
cargo build --manifest-path Cargo.toml --bin openhuman-core
|
||||
|
||||
# Rust — Tauri shell
|
||||
cargo check --manifest-path app/src-tauri/Cargo.toml
|
||||
pnpm rust:check # Tauri shell check
|
||||
```
|
||||
|
||||
Note: `pnpm core:stage` is a no-op (echoes a message). The sidecar was removed in PR #1061; core is linked in-process.
|
||||
|
||||
**Tests**: `pnpm test` (Vitest, app workspace) · `pnpm test:coverage` · `pnpm test:rust` (cargo test via `scripts/test-rust-with-mock.sh`).
|
||||
**Quality**: ESLint + Prettier + Husky in `app`. Pre-push hook runs `pnpm rust:check` — pass `--no-verify` only for unrelated pre-existing breakage.
|
||||
|
||||
### Agent debug runners (`scripts/debug/`)
|
||||
|
||||
Bounded-output wrappers around the project test runners. Stdout stays summary-sized (so it fits in agent context); full output is teed to `target/debug-logs/<kind>-<suffix>-<timestamp>.log`. Add `--verbose` to also stream raw output. Prefer these over invoking Vitest / WDIO / cargo directly when iterating.
|
||||
|
||||
```bash
|
||||
# Vitest
|
||||
pnpm debug unit # full suite
|
||||
pnpm debug unit src/components/Foo.test.tsx # one file (positional pattern)
|
||||
pnpm debug unit -t "renders empty state" # filter by test name
|
||||
pnpm debug unit Foo -t "renders empty" --verbose
|
||||
|
||||
# WDIO E2E (one spec at a time)
|
||||
pnpm debug e2e test/e2e/specs/smoke.spec.ts
|
||||
pnpm debug e2e test/e2e/specs/cron-jobs-flow.spec.ts cron-jobs --verbose
|
||||
|
||||
# cargo tests (delegates to scripts/test-rust-with-mock.sh)
|
||||
pnpm debug rust
|
||||
pnpm debug rust json_rpc_e2e
|
||||
|
||||
# Inspect saved logs
|
||||
pnpm debug logs # list 50 most recent
|
||||
pnpm debug logs last # print most recent (last 400 lines)
|
||||
pnpm debug logs unit # most recent matching prefix "unit"
|
||||
pnpm debug logs last --tail 100
|
||||
```
|
||||
|
||||
Files: `scripts/debug/{cli,unit,e2e,rust,logs,lib}.sh` plus `README.md`. Entry point is `pnpm debug` (`scripts/debug/cli.sh`).
|
||||
|
||||
### Coverage requirement (merge gate)
|
||||
|
||||
PRs must meet **≥ 80% coverage on changed lines**. Enforced by [`.github/workflows/coverage.yml`](.github/workflows/coverage.yml) using `diff-cover` over merged Vitest (`app/coverage/lcov.info`) and `cargo-llvm-cov` (core + Tauri shell) lcov outputs. Below the threshold the PR will not merge — add tests for new/changed lines, not just the happy path.
|
||||
|
||||
---
|
||||
|
||||
## Configuration
|
||||
|
||||
- **[`.env.example`](.env.example)** — Rust core, Tauri shell, backend URL, logging, proxy, storage, AI binary overrides. Load via `source scripts/load-dotenv.sh`.
|
||||
- **[`app/.env.example`](app/.env.example)** — `VITE_*` (core RPC URL, backend URL, Sentry DSN, dev helpers). Copy to `app/.env.local`.
|
||||
|
||||
**Frontend config** is centralized in [`app/src/utils/config.ts`](app/src/utils/config.ts). Read `VITE_*` there and re-export — **never** `import.meta.env` directly elsewhere.
|
||||
|
||||
**Rust config** uses a TOML `Config` struct (`src/openhuman/config/schema/types.rs`) with env overrides (`src/openhuman/config/schema/load.rs`).
|
||||
|
||||
**Agent access mode** — the `[autonomy]` block (`src/openhuman/config/schema/autonomy.rs`) drives the agent's filesystem/shell reach via `SecurityPolicy` (`src/openhuman/security/policy.rs`). Tiers: `level` (`readonly` = read-only / `supervised` = "ask before edit" / `full` = full access) × `workspace_only` × `trusted_roots` (per-folder `read`/`readwrite` grants outside the workspace, overriding `forbidden_paths` for their subtree) × `allow_tool_install` (gates `install_tool`). Edit live via the `config.update_autonomy_settings` RPC or **Settings → Agent access** (`AgentAccessPanel.tsx`); changes swap the process-global policy in `security::live_policy` and apply to new sessions. The default projects home is `~/OpenHuman/projects` (`config::default_projects_dir`, env `OPENHUMAN_PROJECTS_DIR`), auto-created at startup and injected as a ReadWrite trusted root — distinct from the hidden internal `~/.openhuman/workspace`.
|
||||
|
||||
**Action sandbox vs internal workspace** — `Config` exposes two distinct path roots (`src/openhuman/config/schema/types.rs`):
|
||||
|
||||
- **`action_dir`** — the agent's read/write root. **Acting tools (`shell`, `node_exec`, `npm_exec`, `file_write`, `edit_file`, `apply_patch`, `git_operations`, `codegraph_*`) resolve relative paths and default CWD to `action_dir`, not `workspace_dir`.** Defaults to `config::default_action_dir()`, which falls back to `default_projects_dir()` (`~/OpenHuman/projects`); override with `OPENHUMAN_ACTION_DIR`. Auto-created at startup (`channels/runtime/startup.rs`). `action_dir` is independently configurable from `default_projects_dir()` even though they share a default.
|
||||
- **`workspace_dir`** — internal product state (`~/.openhuman/users/<id>/workspace`). Holds `memory/`, `memory_tree/`, `sessions/`, `session_raw/`, `vault/`, `approval/`, `subconscious/`, `task_sources/`, `mcp_clients/`, `cron/`, `devices/`, `whatsapp_data/`, `redirect_links/`, `codegraph/`, `state/`, `.openhuman/`, plus secret files (`core.token`, `dev-keychain.json`, `.env`, `SOUL.md`, `IDENTITY.md`, `HEARTBEAT.md`, `PROFILE.md`). Agent tools **cannot** write here — `SecurityPolicy::is_workspace_internal_path` (`src/openhuman/security/policy.rs:1097`) is the source of truth for the denylist (`WORKSPACE_INTERNAL_DIRS` / `WORKSPACE_INTERNAL_FILES` at `policy.rs:173-202`), enforced fail-closed in `is_path_string_allowed` regardless of tier (`workspace_only`, `full`, or any `trusted_root` that happens to overlap). Both sides are canonicalised before comparison, so symlink escapes don't bypass.
|
||||
|
||||
Overriding `OPENHUMAN_ACTION_DIR` does **not** weaken the internal denylist — `workspace_dir` is always blocked even if `action_dir` is relocated to overlap it. Adding new internal-state subdirectories under `workspace_dir` requires extending `WORKSPACE_INTERNAL_DIRS` (or `WORKSPACE_INTERNAL_FILES`) in the same change; otherwise an agent tool with a `trusted_root` grant could write into the new subtree.
|
||||
|
||||
**Sandbox execution backends** — when an agent's `agent.toml` declares `sandbox_mode = "sandboxed"`, the three shell-family tools (`shell`, `node_exec`, `npm_exec`) route execution through `src/openhuman/sandbox/` instead of the plain `RuntimeAdapter`. The sandbox backend is picked by `sandbox::resolve_sandbox_policy` (`src/openhuman/sandbox/ops.rs`) based on the session origin + `RuntimeConfig`:
|
||||
|
||||
- **Docker** — ephemeral `docker run --rm` containers with `--cap-drop ALL`, `--security-opt no-new-privileges`, `--network none` (remote sessions), read-only rootfs + tmpfs, the action sandbox mounted at the working directory, and labels for orphan cleanup. Preferred for remote / channel / cron / background sessions.
|
||||
- **Local OS jail (`cwd_jail`)** — Landlock on Linux, Seatbelt on macOS, AppContainer on Windows. Rooted at `Config.action_dir`, with output capture via temp files inside the jail (works around Seatbelt's stdio-forwarding limitation). Preferred for interactive desktop sessions.
|
||||
- **Noop** — on platforms where `cwd_jail` has no real backend (or when Docker is unavailable and the local backend is unsupported), the sandbox falls back to a documented passthrough that runs the command with `current_dir = action_dir` and `env_clear()` + the `SANDBOX_ENV_PASSTHROUGH` allow-list. The in-Rust path hardening (`is_path_string_allowed`, `is_workspace_internal_path`, structural `check_gated_command` guards, the unconditional `is_always_forbidden` system/credential block) **still applies** regardless of which sandbox backend is selected — defense-in-depth, not "noop = no defense."
|
||||
|
||||
Default `sandbox_mode = "none"` agents keep the historical behavior (plain `runtime.build_shell_command` path), so the sandbox layer is strictly additive and opt-in per agent. `ShellTool::run_with_security`, `NodeExecTool::execute`, and `NpmExecTool::execute` all check `current_sandbox_mode()` (a `tokio` task-local set by the agent harness) and delegate to their `run_sandboxed()` helper when `SandboxMode::Sandboxed` is active. The security/rate-limit/audit checks run *before* the sandbox routing decision, so a denied command is still denied identically in both paths.
|
||||
|
||||
**Command permission model (deterministic, fail-closed):** `classify_command` buckets a command into `CommandClass` (`Read` / `Write` / `Network` / `Install` / `Destructive`); an unrecognized command is **`Write`**, never `Read`. `gate_decision(class, tier)` → `Allow` / `Prompt` / `Block`: read-only allows only reads; ask-before-edit prompts every act (file *create* is free, *edit-existing* prompts); full runs read+write but **always-asks** Network/Install/Destructive. Acting tools (`shell`/`node_exec`/`npm_exec`/`file_write`/`edit_file`/`apply_patch`/`git_operations`/`curl`) return `external_effect_with_args() == true` for `Prompt` classes so the harness routes them through the `ApprovalGate` *before* `execute()`; read-only `Block` + structural guards (`check_gated_command`) are enforced in-tool. The LLM may pass a `category` (escalate-only: `max(rust_floor, declared)`). System/credential dirs are an **unconditional** cross-platform block (`is_always_forbidden`, trusted-root-proof). Enforcement is in Rust (`classify_command`/`gate_decision`/`check_gated_command`/`is_path_string_allowed`/`validate_path`), never the system prompt.
|
||||
|
||||
> ⚠️ **The approval prompt is ON by default** (opt out with `OPENHUMAN_APPROVAL_GATE=0`/`false`, `jsonrpc.rs`). `ApprovalGate::init_global` installs unless disabled, so `try_global()` is `Some` and the prompt is wired end-to-end; with `OPENHUMAN_APPROVAL_GATE=0` the harness skips the intercept and `Prompt`-class calls **run unprompted**. The gate parks only for **interactive chat turns** (a `tokio` task-local chat context is set in `channels/providers/web.rs`; background triage/cron turns carry no context and are allowed through, not gated). It publishes `DomainEvent::ApprovalRequested`, which `ApprovalSurfaceSubscriber` bridges to the `approval_request` web-channel socket event; the frontend (`ChatApprovalRequestEvent` → `chatRuntime.pendingApprovalByThread` → `ApprovalRequestCard` above the composer) surfaces Approve/Deny, routing to the `openhuman.approval_decide` RPC. A typed `yes`/`no` chat reply is also honoured server-side (web.rs ingress router runs before the "newer request aborts the in-flight turn" path); any other text cancels the parked turn and is taken as a fresh message. Unanswered prompts still park to the 10-min TTL → Deny. Read-only blocking, path hardening, structural guards, and classification **are** live regardless of the flag. Full access ships as documented full-trust (not sandboxed).
|
||||
|
||||
---
|
||||
|
||||
## Testing
|
||||
|
||||
### Unit (Vitest)
|
||||
|
||||
- Co-locate as `*.test.ts` / `*.test.tsx` under `app/src/**`.
|
||||
- Config: `app/test/vitest.config.ts`; setup: `app/src/test/setup.ts`.
|
||||
- Run from repo root: `pnpm test` or `pnpm test:coverage`. (Inside `app/`, `pnpm test:unit` is also defined.)
|
||||
- Prefer behavior over implementation. Use helpers in `app/src/test/`. No real network, no time flakes.
|
||||
|
||||
### Shared mock backend
|
||||
|
||||
Used by both unit and Rust tests.
|
||||
- Core: `scripts/mock-api-core.mjs` · server: `scripts/mock-api-server.mjs` · E2E wrapper: `app/test/e2e/mock-server.ts`.
|
||||
- Admin: `GET /__admin/health`, `POST /__admin/reset`, `POST /__admin/behavior`, `GET /__admin/requests`.
|
||||
- Run manually: `pnpm mock:api`.
|
||||
|
||||
### E2E (WDIO — dual platform)
|
||||
|
||||
Full guide: [`gitbooks/developing/e2e-testing.md`](gitbooks/developing/e2e-testing.md).
|
||||
- **Linux (CI)**: `tauri-driver` (WebDriver :4444).
|
||||
- **macOS (local)**: Appium Mac2 (XCUITest :4723) on the `.app` bundle.
|
||||
- Specs: `app/test/e2e/specs/*.spec.ts`. Helpers in `app/test/e2e/helpers/`. Config: `app/test/wdio.conf.ts`.
|
||||
|
||||
```bash
|
||||
pnpm test:e2e:build
|
||||
bash app/scripts/e2e-run-spec.sh test/e2e/specs/smoke.spec.ts smoke
|
||||
pnpm test:e2e:all:flows
|
||||
docker compose -f e2e/docker-compose.yml run --rm e2e # Linux E2E on macOS
|
||||
```
|
||||
|
||||
Use `element-helpers.ts` (`clickNativeButton`, `waitForWebView`, `clickToggle`) — never raw `XCUIElementType*`. Assert UI outcomes and mock effects.
|
||||
|
||||
### Deterministic core reset (E2E)
|
||||
|
||||
`app/scripts/e2e-run-spec.sh` creates and cleans a temp `OPENHUMAN_WORKSPACE` by default. `OPENHUMAN_WORKSPACE` redirects core config + storage away from `~/.openhuman`. Each spec gets a fresh in-process core inside the freshly-built Tauri bundle.
|
||||
|
||||
### Rust tests with mock
|
||||
|
||||
```bash
|
||||
pnpm test:rust
|
||||
bash scripts/test-rust-with-mock.sh --test json_rpc_e2e
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Frontend (`app/src/`)
|
||||
|
||||
**Provider chain** (`App.tsx`):
|
||||
`Sentry.ErrorBoundary` → `Redux Provider` → `PersistGate` (with `PersistRehydrationScreen`) → `BootCheckGate` → `CoreStateProvider` → `SocketProvider` → `ChatRuntimeProvider` → `HashRouter` → `CommandProvider` → `ServiceBlockingGate` → `AppShell` (`AppRoutes` + `BottomTabBar` + walkthrough/mascot/snackbars).
|
||||
|
||||
No `UserProvider` / `AIProvider` / `SkillProvider` — auth and core snapshot live in `CoreStateProvider`, fetched via `fetchCoreAppSnapshot()` RPC (auth tokens are NOT in redux-persist; they live in the in-process core).
|
||||
|
||||
**State** (`store/`): Redux Toolkit slices — `accounts`, `channelConnections`, `chatRuntime`, `coreMode`, `deepLinkAuth`, `mascot`, `notification`, `providerSurface`, `socket`, `thread`. Persisted slices via redux-persist. Prefer Redux over ad-hoc `localStorage` (exception: ephemeral UI state like upsell dismiss flags).
|
||||
|
||||
**Services** (`services/`): singletons — `apiClient`, `socketService`, `coreRpcClient` + `coreCommandClient` (HTTP bridge to in-process core via Tauri IPC), `chatService`, `analytics`, `notificationService`, `webviewAccountService`, `daemonHealthService`, plus domain `api/*` clients.
|
||||
|
||||
**MCP** (`lib/mcp/`): JSON-RPC transport, validation, types over Socket.io.
|
||||
|
||||
**Routing** (`AppRoutes.tsx`, HashRouter): `/` (Welcome), `/onboarding/*`, `/home`, `/human`, `/intelligence`, `/skills`, `/chat` (unified agent + connected web apps, replaces old `/conversations` + `/accounts`), `/channels`, `/invites`, `/notifications`, `/rewards`, `/webhooks` (redirects to `/settings/webhooks-triggers`), `/settings/*`. Default catch-all is `DefaultRedirect`. There is no `/login`, no `/mnemonic` (recovery phrase moved to Settings), no `/agents`, no `/conversations`.
|
||||
|
||||
**AI config**: bundled prompts in `src/openhuman/agent/prompts/` (also bundled via `app/src-tauri/tauri.conf.json` `resources`). Loaders in `app/src/lib/ai/` use `?raw` imports, optional remote fetch, and `ai_get_config` / `ai_refresh_config` in Tauri.
|
||||
|
||||
---
|
||||
|
||||
## Tauri shell (`app/src-tauri/`)
|
||||
|
||||
Thin desktop host. Top-level modules: `core_process`, `core_rpc`, `cdp`, `cef_preflight`, `cef_profile`, `dictation_hotkeys`, `file_logging`, `mascot_native_window`, `native_notifications`, `notification_settings`, `process_kill`, `process_recovery`, `screen_capture`, `window_state`, plus the per-provider scanner modules (`discord_scanner`, `gmessages_scanner`, `imessage_scanner`, `meet_scanner`, `slack_scanner`, `telegram_scanner`, `whatsapp_scanner`), `meet_audio` / `meet_call` / `meet_video`, `fake_camera`, `webview_accounts`, `webview_apis`.
|
||||
|
||||
**Core lifecycle**: `core_process::CoreProcessHandle` spawns the JSON-RPC server as an in-process tokio task and authenticates inbound RPC with a per-launch hex bearer. The bearer is generated in `CoreProcessHandle::new()` and handed to the embedded server in-memory through `run_server_embedded_with_ready(rpc_token: Some(_))` — never set on the process env. On stale-listener detection (#1130) the handle revalidates the PID before force-killing so PID reuse can't kill an unrelated process. `restart_core_process` / `start_core_process` Tauri commands let the frontend cycle it for updates.
|
||||
|
||||
Registered IPC (see [`gitbooks/developing/architecture/tauri-shell.md`](gitbooks/developing/architecture/tauri-shell.md)) includes `greet`, `write_ai_config_file`, `ai_get_config`, `ai_refresh_config`, `core_rpc_relay`, `core_rpc_token`, `start_core_process`, `restart_core_process`, window commands, and `openhuman_*` daemon helpers. Always use `invoke('core_rpc_relay', ...)` for in-process RPC (avoids CORS preflight that `fetch()` would trigger).
|
||||
|
||||
### CEF child webviews — no new JS injection
|
||||
|
||||
Embedded provider webviews (`acct_*`, loading third-party origins like `web.telegram.org`, `linkedin.com`, `slack.com`, …) **must not** grow any new JavaScript injection. Do not add new `.js` files under `app/src-tauri/src/webview_accounts/`, do not append new blocks to `build_init_script` / `RUNTIME_JS`, and do not dispatch scripts via CDP `Page.addScriptToEvaluateOnNewDocument` / `Runtime.evaluate` for these webviews. The migrated providers (whatsapp, telegram, slack, discord, browserscan) load with **zero** injected JS under CEF by design — all scraping and observability runs natively via CDP in the per-provider scanner modules, and anything host-controlled that runs inside a third-party origin is a scraping/attack-surface liability.
|
||||
|
||||
New behavior for these webviews lives in:
|
||||
|
||||
- **CEF handlers** — `on_navigation`, `on_new_window`, `LoadHandler::OnLoadStart`, `CefRequestHandler::*` (wired in `webview_accounts/mod.rs`).
|
||||
- **CDP from the scanner side** — `Network.*`, `Emulation.*`, `Input.*`, `Page.*` driven by the per-provider `*_scanner/` modules.
|
||||
- **Rust-side notification/IPC hooks** — never cross into the renderer.
|
||||
|
||||
If a feature truly cannot be built this way (e.g. intercepting a click the page's JS preventDefaults), the correct answer is to **surface the limitation**, not to ship an init script. Legacy injection that already exists for non-migrated providers (`gmail`, `linkedin`, `google-meet` recipe files plus the `runtime.js` bridge) is grandfathered but should shrink, not grow.
|
||||
|
||||
Watch out for Tauri plugins that inject JS by default. `tauri-plugin-opener` ships `init-iife.js` (a global click listener that calls `plugin:opener|open_url` via HTTP-IPC) unless you build it with `.open_js_links_on_click(false)`. Any new plugin added to `app/src-tauri/src/lib.rs` must be audited for a `js_init_script` call — if found, opt out or configure around it.
|
||||
|
||||
---
|
||||
|
||||
## Rust core (`src/`)
|
||||
|
||||
- **`src/openhuman/`** — Domain logic. Current domains: `about_app`, `accessibility`, `agent`, `app_state`, `approval`, `autocomplete`, `billing`, `channels`, `composio`, `config`, `context`, `cost`, `credentials`, `cron`, `doctor`, `embeddings`, `encryption`, `health`, `heartbeat`, `integrations`, `learning`, `local_ai`, `meet`, `meet_agent`, `memory`, `migration`, `node_runtime`, `notifications`, `overlay`, `people`, `prompt_injection`, `provider_surfaces`, `providers`, `redirect_links`, `referral`, `routing`, `scheduler_gate`, `screen_intelligence`, `security`, `service`, `skills`, `socket`, `subconscious`, `team`, `text_input`, `threads`, `tokenjuice`, `tool_timeout`, `tools`, `tree_summarizer`, `update`, `voice`, `wallet`, `webhooks`, `webview_accounts`, `webview_apis`, `webview_notifications`. RPC controllers in per-domain `rpc.rs` / `schemas.rs`; use `RpcOutcome<T>` per [`AGENTS.md`](AGENTS.md).
|
||||
- **Skills runtime removed**: the QuickJS / `rquickjs` runtime that previously executed skill packages is gone. `src/openhuman/skills/` is now a metadata-only domain (`ops_create`, `ops_discover`, `ops_install`, `ops_parse`, `inject`, `schemas`, `types`) — see the module header comment "Legacy skill metadata helpers retained after QuickJS runtime removal."
|
||||
- **Module layout rule**: new functionality goes in a **dedicated subdirectory** (`openhuman/<domain>/mod.rs` + siblings). **Do not** add new standalone `*.rs` files at `src/openhuman/` root (`dev_paths.rs` and `util.rs` are grandfathered, not a template).
|
||||
- **Controller schema contract**: shared types in `src/core/types.rs` / `src/core/mod.rs` (`ControllerSchema`, `FieldSchema`, `TypeSchema`).
|
||||
- **Domain schema files**: per-domain `schemas.rs` (e.g. `src/openhuman/cron/schemas.rs`), exported from domain `mod.rs`.
|
||||
- **Controller-only exposure**: expose features to CLI and JSON-RPC via the controller registry. **Do not** add domain branches in `src/core/cli.rs` / `src/core/jsonrpc.rs`.
|
||||
- **Light `mod.rs`**: keep domain `mod.rs` export-focused. Operational code in `ops.rs`, `store.rs`, `types.rs`, etc. See **Canonical module shape** below for the full per-file contract.
|
||||
- **`src/core/`** — Transport only. Modules: `all`, `all_tests`, `auth`, `autocomplete_cli_adapter`, `cli`, `cli_tests`, `dispatch`, `event_bus/`, `jsonrpc`, `jsonrpc_tests`, `legacy_aliases`, `logging`, `memory_cli`, `observability`, `rpc_log`, `shutdown`, `socketio`, `types`, plus `agent_cli`. No heavy domain logic here. (There is no `src/core_server/` — older docs that reference `core_server` mean `src/core/`.)
|
||||
- **Memory source identity**: per-item selector IDs (Notion page, GitHub issue/PR, Linear issue, ClickUp task) are document dedupe keys only. Do not use them as source tree / raw archive / Obsidian source-tag identity; set `metadata.path_scope` via `ingest_document_with_scope` to the stable collection scope such as `<provider>:<connection_id>` or `github.com/<owner>/<repo>`.
|
||||
|
||||
### Canonical module shape
|
||||
|
||||
Each high-level domain under `src/openhuman/<domain>/` should follow this file contract. Only `mod.rs` and tests are universal; the rest exist **only when applicable** — do not create empty placeholder files (e.g. a stateless domain has no `store.rs`, a domain that exposes no agent tools has no `tools.rs`).
|
||||
|
||||
| File | When | Role |
|
||||
| --- | --- | --- |
|
||||
| `mod.rs` | always | Export-focused **only**: module docstring + `mod`/`pub mod` decls + `pub use` re-exports, plus the `all_<domain>_controller_schemas` / `all_<domain>_registered_controllers` pair when RPC-facing. **No business logic, no domain-state statics, no domain `impl` blocks.** |
|
||||
| `types.rs` | domain has its own types | Serde domain types. |
|
||||
| `store.rs` | domain persists state | Persistence layer. |
|
||||
| `ops.rs` | domain has logic / handlers | Business logic + entry points returning `RpcOutcome<T>`. **Canonical handler file** (`ops.rs` is the majority convention; `rpc.rs` is legal only where a domain separates a pure-domain API from ops, e.g. `cron` does `pub use ops as rpc`). |
|
||||
| `schemas.rs` | RPC-facing | Controller schemas + `handle_*` fns delegating to `ops.rs` (see **Controller migration checklist**). |
|
||||
| `tools.rs` | domain owns agent tools | Domain-owned tool impls live here (+ optional `tools/` submodules), re-exported via `src/openhuman/tools/mod.rs` (see AGENTS.md "Tool ownership rule"). Only genuinely cross-cutting tool families (filesystem, browser/computer, generic system/network) stay in `src/openhuman/tools/impl/`. |
|
||||
| `bus.rs` | domain has event subscribers | `EventHandler` impls (see **Event bus**). |
|
||||
| tests | new/changed behavior | Inline `#[cfg(test)] mod tests` (small modules) **or** a sibling `<file>_tests.rs` via `#[cfg(test)] #[path = "<file>_tests.rs"] mod tests;` (large suites). Both are legal. |
|
||||
|
||||
Two clarifications:
|
||||
- **Inline tests do not count against "light `mod.rs`".** A `mod.rs` whose non-test body is pure re-exports is already compliant; moving a large inline suite into a sibling `mod_tests.rs` is tidiness, not a correctness requirement.
|
||||
- **Narrow thin-facade exception:** pure dispatch forwarders (pick an implementation and forward — no domain state, no I/O of their own) MAY stay in `mod.rs` when being that facade is the module's whole purpose (e.g. `cwd_jail::spawn` / `spawn_with` / `default_backend`). Justify it in the module docstring.
|
||||
|
||||
### Controller migration checklist
|
||||
|
||||
- `src/openhuman/<domain>/mod.rs`: add `mod schemas;`, re-export `all_controller_schemas as all_<domain>_controller_schemas` and `all_registered_controllers as all_<domain>_registered_controllers`.
|
||||
- `src/openhuman/<domain>/schemas.rs` defines `schemas`, `all_controller_schemas`, `all_registered_controllers`, and `handle_*` fns delegating to domain `rpc.rs`.
|
||||
- Wire exports into `src/core/all.rs`. Remove migrated branches from `src/core/dispatch.rs`.
|
||||
|
||||
### Event bus (`src/core/event_bus/`)
|
||||
|
||||
Typed pub/sub + in-process typed request/response. Both singletons — use module-level functions; never construct `EventBus` / `NativeRegistry` directly.
|
||||
|
||||
- **Broadcast** (`publish_global` / `subscribe_global`) — fire-and-forget. Many subscribers, no return.
|
||||
- **Native request/response** (`register_native_global` / `request_native_global`) — one-to-one typed dispatch keyed by method string. Zero serialization — trait objects, `mpsc::Sender`, `oneshot::Sender` pass through unchanged. Internal-only; JSON-RPC-facing work goes through `src/core/all.rs`.
|
||||
|
||||
Core types (all in `src/core/event_bus/`):
|
||||
|
||||
| Type | File | Purpose |
|
||||
| --- | --- | --- |
|
||||
| `DomainEvent` | `events.rs` | `#[non_exhaustive]` enum of all cross-module events |
|
||||
| `EventBus` | `bus.rs` | Singleton over `tokio::sync::broadcast`; ctor is `pub(crate)` |
|
||||
| `NativeRegistry` / `NativeRequestError` | `native_request.rs` | Typed request/response registry by method name |
|
||||
| `EventHandler` | `subscriber.rs` | Async trait with optional `domains()` filter |
|
||||
| `SubscriptionHandle` | `subscriber.rs` | RAII — drops cancel the subscriber |
|
||||
| `TracingSubscriber` | `tracing.rs` | Built-in debug logger |
|
||||
|
||||
Singleton API: `init_global(capacity)`, `publish_global(event)`, `subscribe_global(handler)`, `register_native_global(method, handler)`, `request_native_global(method, req)`, `global()` / `native_registry()`.
|
||||
|
||||
Domains: `agent`, `memory`, `channel`, `cron`, `skill`, `tool`, `webhook`, `system`.
|
||||
|
||||
Each domain owns a `bus.rs` with its `EventHandler` impls — e.g. `cron/bus.rs` (`CronDeliverySubscriber`), `webhooks/bus.rs` (`WebhookRequestSubscriber`), `channels/bus.rs` (`ChannelInboundSubscriber`). Convention: `<Purpose>Subscriber` + `name()` returning `"<domain>::<purpose>"`.
|
||||
|
||||
**Adding events**: add variants to `DomainEvent`, extend the `domain()` match, create `<domain>/bus.rs`, register subscribers at startup, publish via `publish_global`.
|
||||
|
||||
**Adding a native handler**: define request/response types in the domain (owned fields, `Arc`s, channels — not borrows; `Send + 'static`, not `Serialize`). Register at startup keyed by `"<domain>.<verb>"`. Callers dispatch via `request_native_global`.
|
||||
|
||||
**Tests**: re-register the same method to override; or construct a fresh `NativeRegistry::new()` for isolation.
|
||||
|
||||
---
|
||||
|
||||
## Design
|
||||
|
||||
Premium, calm visual language — ocean primary `#4A83DD`, sage / amber / coral semantics, Inter + Cabinet Grotesk + JetBrains Mono, Tailwind with custom radii/spacing/shadows. Implementation tokens live in [`app/tailwind.config.js`](app/tailwind.config.js).
|
||||
|
||||
## Shell vs app code
|
||||
|
||||
Tauri/Rust in the shell is a **delivery vehicle** (windowing, process lifecycle, IPC). Keep UI behavior and product logic in TypeScript/React (`app/`). Only grow Rust in the shell for hard platform/security reasons.
|
||||
|
||||
## Git workflow
|
||||
|
||||
This file is loaded into every contributor's Claude Code session, so the instructions below are written generically: `<your-username>` means **your** GitHub username (the owner of your fork), not any specific maintainer. Adapt the literal commands accordingly.
|
||||
|
||||
**One-time remote setup.** Contribute via your own fork of `tinyhumansai/openhuman`. Recommended remote layout:
|
||||
|
||||
```
|
||||
origin git@github.com:<your-username>/openhuman.git (your fork — push here)
|
||||
upstream git@github.com:tinyhumansai/openhuman.git (fetch-only; never push)
|
||||
```
|
||||
|
||||
If you cloned the upstream directly, fix it once:
|
||||
|
||||
```bash
|
||||
git remote rename origin upstream
|
||||
git remote add origin git@github.com:<your-username>/openhuman.git
|
||||
git fetch upstream
|
||||
```
|
||||
|
||||
See [`CONTRIBUTING.md`](CONTRIBUTING.md) for the full new-contributor walkthrough.
|
||||
|
||||
- **Never write code on `main`.** Before making any code changes, branch off the latest upstream `main` (`git fetch upstream && git checkout -b <branch> upstream/main`). All work happens on that feature branch; `main` stays clean and only advances via merged PRs.
|
||||
- Issues and PRs on upstream **[tinyhumansai/openhuman](https://github.com/tinyhumansai/openhuman)** — not a fork — unless explicitly told otherwise.
|
||||
- Issue templates: [`.github/ISSUE_TEMPLATE/feature.md`](.github/ISSUE_TEMPLATE/feature.md), [`.github/ISSUE_TEMPLATE/bug.md`](.github/ISSUE_TEMPLATE/bug.md). PR template: [`.github/PULL_REQUEST_TEMPLATE.md`](.github/PULL_REQUEST_TEMPLATE.md). AI-authored text should follow them verbatim.
|
||||
- PRs target **`main`** of `tinyhumansai/openhuman`.
|
||||
- **Push branches to `origin` (your fork), never to `upstream` (`tinyhumansai/openhuman`).** PRs are opened against `tinyhumansai/openhuman:main` with `--head <your-username>:<branch>` so the source is the fork. Direct pushes to upstream pollute its branch list and skip code-review boundaries. Treat the `upstream` remote as fetch-only.
|
||||
- **When the user asks you to push or open a PR, resolve blockers and push — don't prompt for permission.** If a pre-push hook fails on something unrelated to your changes (e.g. pre-existing breakage on `main` in code you didn't touch), push with `--no-verify` and call it out in the PR body. If the hook fails on your own changes, fix them and push again. Don't ask the user whether to bypass — just do the right thing and tell them what you did.
|
||||
|
||||
---
|
||||
|
||||
## Coding philosophy
|
||||
|
||||
- **Unix-style modules**: small, sharp-responsibility units composed through clear boundaries.
|
||||
- **Tests before the next layer**: ship unit tests for new/changed behavior before stacking features. Untested code is incomplete.
|
||||
- **Docs with code**: new/changed behavior ships with matching rustdoc / code comments; update `AGENTS.md` or architecture docs when rules or user-visible behavior change.
|
||||
|
||||
---
|
||||
|
||||
## Debug logging (must follow)
|
||||
|
||||
- Default to **verbose diagnostics** on new/changed flows so issues are easy to trace end-to-end.
|
||||
- Log entry/exit, branches, external calls, retries/timeouts, state transitions, errors.
|
||||
- Use stable grep-friendly prefixes (`[domain]`, `[rpc]`, `[ui-flow]`) and correlation fields (request IDs, method names, entity IDs).
|
||||
- Rust: `log` / `tracing` at `debug` / `trace`. `app/`: namespaced `debug` + dev-only detail.
|
||||
- **Never** log secrets or full PII — redact.
|
||||
- Changes lacking diagnosis logging are incomplete.
|
||||
|
||||
---
|
||||
|
||||
## Feature design workflow
|
||||
|
||||
Specify → prove in Rust → prove over RPC → surface in the UI → test.
|
||||
|
||||
1. **Specify against the current codebase** — ground in existing domains, controller/registry patterns, JSON-RPC naming (`openhuman.<namespace>_<function>`). No parallel architectures.
|
||||
2. **Implement in Rust** — domain logic under `src/openhuman/<domain>/`, schemas + handlers in the registry, unit tests until correct in isolation.
|
||||
3. **JSON-RPC E2E** — extend [`tests/json_rpc_e2e.rs`](tests/json_rpc_e2e.rs) / [`scripts/test-rust-with-mock.sh`](scripts/test-rust-with-mock.sh) so RPC methods match what the UI will call.
|
||||
4. **UI in Tauri app** — React screens/state using `core_rpc_relay` / `coreRpcClient`. Keep rules in the core.
|
||||
5. **App unit tests** — Vitest.
|
||||
6. **App E2E** — desktop specs for user-visible flows.
|
||||
|
||||
**Capability catalog**: when a change adds/removes/renames a user-facing feature, update `src/openhuman/about_app/` in the same work.
|
||||
|
||||
**Planning rule**: up front, define the **E2E scenarios (core RPC + app)** that cover the full intended scope — happy paths, failure modes, auth gates, regressions. Not testable end-to-end ⇒ incomplete spec or too-large cut.
|
||||
|
||||
---
|
||||
|
||||
## Key patterns
|
||||
|
||||
- **File size**: prefer ≤ ~500 lines; split growing modules.
|
||||
- **Pre-merge** (code changes): Prettier, ESLint, `tsc --noEmit` in `app/`; `cargo fmt` + `cargo check` for changed Rust.
|
||||
- **No dynamic imports** in production `app/src` code — static `import` / `import type` only. No `import()`, `React.lazy(() => import(...))`, `await import(...)`. For heavy optional paths, use a static import and guard the call site with `try/catch` or a runtime check. *Exceptions*: Vitest harness patterns in `*.test.ts` / `__tests__` / `test/setup.ts`; ambient `typeof import('…')` in `.d.ts`; config files (e.g. `tailwind.config.js` JSDoc).
|
||||
- **Dual socket sync**: when changing the realtime protocol, keep `socketService` / MCP transport aligned with core socket behavior (see `gitbooks/developing/architecture.md` dual-socket section).
|
||||
- **i18n for all UI text**: every user-visible string in `app/src/**` (headings, labels, button text, placeholders, status chips, toasts, error messages, dialog copy) must go through `useT()` from `app/src/lib/i18n/I18nContext`. Hard-coded literals in JSX or `label=`/`placeholder=`/`aria-label=` props are not allowed. Add the key to [`app/src/lib/i18n/en.ts`](app/src/lib/i18n/en.ts) **and a real translation to every other locale file** in the same PR (see the next bullet — English-only fallback is the runtime safety net for *missing* keys, not an excuse to ship untranslated values). Exceptions: developer-only debug logs, code identifiers, and non-display data (URLs, slugs, technical sentinel values).
|
||||
- **i18n locale files — update ALL locales with REAL translations**: each locale is a **single flat file** at `app/src/lib/i18n/<locale>.ts` (`en.ts` is the source of truth; the chunked `chunks/<locale>-N.ts` layout was retired). When adding or changing keys in `en.ts`, you **must also** add the same key to every non-English locale file **with an actual, correct translation in that language — NOT an English placeholder**. Do not copy the English string into `ar.ts`/`de.ts`/… and "leave it for translators"; translate it (the short UI strings here are well within reach — translate them, and only fall back to English for genuine brand names / commands / paths / units, which belong in the `INTENTIONAL_ENGLISH` allowlist). CI enforces this two ways: `pnpm i18n:check` fails on a missing or extra key in any locale (parity), and **`pnpm i18n:english:check`** ([`scripts/i18n-find-english.ts`](scripts/i18n-find-english.ts)) fails on any value still rendering English — including *stale* English (translated from an older en string that since changed). The English-detection gate uses script-coverage for non-Latin locales and English-only function words for Latin locales; the `INTENTIONAL_ENGLISH` allowlist is **only** for genuinely-English literals (brand names / commands / paths / units / cognates) — never use it to silence an untranslated string. Locales: `ar`, `bn`, `de`, `es`, `fr`, `hi`, `id`, `it`, `ko`, `pl`, `pt`, `ru`, `zh-CN`.
|
||||
|
||||
---
|
||||
|
||||
## Platform notes
|
||||
|
||||
- **Vendored CEF-aware `tauri-cli`**: runtime is CEF; only the vendored CLI at `app/src-tauri/vendor/tauri-cef/crates/tauri-cli` bundles Chromium into `Contents/Frameworks/`. Stock `@tauri-apps/cli` produces a broken bundle (panic in `cef::library_loader::LibraryLoader::new`). `pnpm dev:app` and all `cargo tauri` scripts call `pnpm tauri:ensure` which runs [`scripts/ensure-tauri-cli.sh`](scripts/ensure-tauri-cli.sh). If overwritten, reinstall with `cargo install --locked --path app/src-tauri/vendor/tauri-cef/crates/tauri-cli`.
|
||||
- **macOS deep links**: often require a built `.app` bundle, not just `tauri dev`.
|
||||
- **Windows deep links**: `openhuman://` is registered to `HKCU\Software\Classes\openhuman\shell\open\command` by `tauri-plugin-deep-link::register_all` at first launch (per-user, no UAC). The Tauri shell now reads that key back after `register_all` returns and emits `log::error!` with the actual state (`NotRegistered` / `MissingCommand` / `Stale` / `ReadError`) when the value is missing or doesn't point at the running exe — without it, OAuth callbacks via `openhuman://auth?…` never reach the app (issue #2699). The check lives in [`app/src-tauri/src/deep_link_registration_check.rs`](app/src-tauri/src/deep_link_registration_check.rs); a manual repair script for affected users is in [`gitbooks/overview/troubleshooting-sign-in.md`](gitbooks/overview/troubleshooting-sign-in.md).
|
||||
- **Tauri environment guard**: use `isTauri()` (from `app/src/services/webviewAccountService.ts`) or wrap `invoke(...)` in `try/catch`; do not check `window.__TAURI__` directly — it is not present at module load and bypasses the established wrapper contract.
|
||||
- **Core is in-process** (no sidecar): `core_rpc` reaches the embedded server at `http://127.0.0.1:<port>/rpc` with bearer auth. The Tauri shell hands the bearer to the embedded server in-memory (no `OPENHUMAN_CORE_TOKEN` on the process env). `scripts/stage-core-sidecar.mjs` no longer exists; `pnpm core:stage` is a no-op echo. To run the core standalone for debugging, use `./target/debug/openhuman-core serve` (token at `{workspace}/core.token`, default `~/.openhuman-staging/core.token` under `OPENHUMAN_APP_ENV=staging`); docker / cloud deployments still supply the bearer via `OPENHUMAN_CORE_TOKEN` in the environment (operator-supplied).
|
||||
@@ -1,171 +0,0 @@
|
||||
# CODEX Workpad
|
||||
|
||||
> **Format**: chronological log of Codex/AI-driven sweeps. Entries below are historical — read them as "what happened on this date" rather than as a description of current repo state. For current state see [`CLAUDE.md`](CLAUDE.md), [`AGENTS.md`](AGENTS.md), and [`.claude/memory.md`](.claude/memory.md).
|
||||
>
|
||||
> Append new entries at the bottom with a date heading and a short Scope / Validation / Follow-up structure. Do not edit older entries in place.
|
||||
|
||||
## Portfolio Readiness Pass - 2026-05-12
|
||||
|
||||
### Scope
|
||||
|
||||
- No product functionality changes.
|
||||
- Validation-focused pass for presentation readiness.
|
||||
- Existing dirty `.gitignore` and `docs/architecture/` changes were left intact.
|
||||
|
||||
### Validation Evidence
|
||||
|
||||
Commands run:
|
||||
|
||||
```bash
|
||||
pnpm run typecheck
|
||||
pnpm run lint
|
||||
pnpm run test
|
||||
pnpm run test:rust
|
||||
git diff --check
|
||||
```
|
||||
|
||||
Results:
|
||||
|
||||
- `pnpm run typecheck`: passed.
|
||||
- `pnpm run lint`: passed with 38 warnings, mostly React compiler `set-state-in-effect` warnings plus one stale eslint-disable.
|
||||
- `pnpm run test`: passed, 214 test files, 1 skipped; 1,977 tests passed, 3 skipped.
|
||||
- `pnpm run test:rust`: passed via `scripts/test-rust-with-mock.sh`.
|
||||
- `git diff --check`: passed.
|
||||
|
||||
### Follow-Up Debt
|
||||
|
||||
- Triage React compiler lint warnings before treating lint output as presentation-clean.
|
||||
- Decide whether Node `localStorage is not available` warnings in Vitest should be silenced with an explicit test environment/localStorage configuration.
|
||||
|
||||
## Lint Warning Cleanup - 2026-05-13
|
||||
|
||||
Original source checkout scope:
|
||||
|
||||
- Removed one stale `eslint-disable-next-line no-console` directive from
|
||||
`app/src/services/meetCallService.ts`.
|
||||
- No behavior change.
|
||||
|
||||
Validation:
|
||||
|
||||
```bash
|
||||
pnpm run lint
|
||||
```
|
||||
|
||||
Result: passed with 37 warnings, down from 38. Remaining warnings are React
|
||||
compiler `set-state-in-effect` warnings plus one `no-explicit-any` warning.
|
||||
|
||||
Follow-up cleanup:
|
||||
|
||||
- Replaced the remaining `no-explicit-any` cast in the source checkout's
|
||||
`app/src/lib/mcp/transport.ts` with a typed MCP event handler map. This
|
||||
transport cleanup was already obsolete on the clean `origin/main` extraction
|
||||
branch and is not part of the PR branch.
|
||||
- `pnpm run lint`: passed with 36 warnings, down from 37. Remaining warnings
|
||||
are React compiler `set-state-in-effect` warnings.
|
||||
- `pnpm run typecheck`: passed.
|
||||
- `git diff --check`: passed.
|
||||
|
||||
Follow-up cleanup:
|
||||
|
||||
- Moved mnemonic/recovery-phrase mode resets out of effects and into the mode
|
||||
switch event handlers in `app/src/pages/Mnemonic.tsx` and
|
||||
`app/src/components/settings/panels/RecoveryPhrasePanel.tsx`.
|
||||
- Removed dead sidebar label reset state from `app/src/pages/Conversations.tsx`
|
||||
so the fixed tab model owns empty label categories directly.
|
||||
- `pnpm run lint`: passed with 33 warnings, down from 36. Remaining warnings
|
||||
are React compiler `set-state-in-effect` warnings.
|
||||
|
||||
## Portfolio Readiness Note - 2026-05-13
|
||||
|
||||
Added `docs/PORTFOLIO_READINESS.md` with:
|
||||
|
||||
- validation evidence,
|
||||
- cleanup summary,
|
||||
- remaining React compiler warning debt,
|
||||
- public claim boundary,
|
||||
- next lint-policy slice.
|
||||
|
||||
No product behavior changes.
|
||||
|
||||
## Clean PR Extraction - 2026-05-13
|
||||
|
||||
Implementation branch: `codex/openhuman-portfolio-lint-readiness-upstream`.
|
||||
Base: `upstream/main` at `83bc5648`.
|
||||
|
||||
The source checkout was already on `codex/operator-mvp-plan` with generated
|
||||
architecture artifacts and several cleanup changes. The clean extraction keeps
|
||||
only the still-relevant portfolio lint/readiness slice:
|
||||
|
||||
- `.gitignore` ignores local `.cocoindex_code/` index output.
|
||||
- `app/src/components/settings/panels/RecoveryPhrasePanel.tsx` moves recovery
|
||||
phrase mode resets into the explicit mode switch handler.
|
||||
- `app/src/pages/Mnemonic.tsx` moves mnemonic mode resets into the explicit
|
||||
mode switch handler.
|
||||
- `app/src/pages/Conversations.tsx` removes dead label reset state now that the
|
||||
fixed tab model owns label categories.
|
||||
- `docs/PORTFOLIO_READINESS.md` records validation evidence and the remaining
|
||||
warning boundary.
|
||||
|
||||
Excluded:
|
||||
|
||||
- Generated `docs/architecture/` scan artifacts.
|
||||
- Obsolete source-checkout changes that were already absent from current
|
||||
`upstream/main`, including the MCP transport cast cleanup and removed meet call
|
||||
service cleanup.
|
||||
- Broader Core RPC, config, or scanner-memory architecture refactors.
|
||||
|
||||
Gemini secondary review:
|
||||
|
||||
- Pre-review was attempted with `gemini-2.5-flash`, but Gemini repeatedly
|
||||
returned 429 model-capacity errors. This branch therefore has local validation
|
||||
evidence but no completed Gemini review for the OpenHuman slice.
|
||||
|
||||
Validation on the upstream-based clean extraction branch:
|
||||
|
||||
```bash
|
||||
pnpm install
|
||||
```
|
||||
|
||||
Result: passed after the upstream rebase installed the missing current
|
||||
workspace packages from the local pnpm store. Build scripts were left
|
||||
unapproved.
|
||||
|
||||
```bash
|
||||
pnpm run lint
|
||||
```
|
||||
|
||||
Result: passed with 35 warnings. Remaining warnings are
|
||||
`react-hooks/set-state-in-effect`.
|
||||
|
||||
```bash
|
||||
pnpm run typecheck
|
||||
```
|
||||
|
||||
Result: passed.
|
||||
|
||||
```bash
|
||||
pnpm --filter openhuman-app exec vitest run --config test/vitest.config.ts src/pages/__tests__/Conversations.test.tsx src/pages/__tests__/Conversations.render.test.tsx src/components/settings/panels/__tests__/RecoveryPhrasePanel.test.tsx
|
||||
```
|
||||
|
||||
Result: passed, `3` files and `24` tests. Vitest still emitted the known Node
|
||||
`localStorage is not available` warning.
|
||||
|
||||
```bash
|
||||
git diff --check
|
||||
```
|
||||
|
||||
Result: passed.
|
||||
|
||||
```bash
|
||||
pnpm --filter openhuman-app exec prettier --check .
|
||||
```
|
||||
|
||||
Result: passed from `app/`.
|
||||
|
||||
```bash
|
||||
cargo fmt --manifest-path Cargo.toml --all --check
|
||||
cargo fmt --manifest-path app/src-tauri/Cargo.toml --all --check
|
||||
cargo check --manifest-path app/src-tauri/Cargo.toml
|
||||
```
|
||||
|
||||
Result: passed. `cargo check` emitted existing Rust warnings.
|
||||
Reference in New Issue
Block a user