mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-27 21:08:00 +00:00
refactor: split tauri host from openhuman_core runtime (#43)
* chore: add Cargo.toml and Cargo.lock for rust-core workspace - Introduced a new workspace for the rust-core module with its own Cargo.toml. - Added Cargo.lock to manage dependencies for the rust-core module. - Updated .gitignore to exclude the target directory generated by Cargo. - Modified GitHub Actions workflow to build from the new rust-core path instead of src-tauri. * feat: enhance memory and authentication models in rust-core - Added new `memory` module to handle persistent memory operations for skills, including methods for storing and querying skill data. - Introduced `models` module with `auth` and `socket` submodules to manage user session and socket connection states. - Updated `lib.rs` to include new modules and ensure proper integration within the rust-core workspace. - Modified `eslint.config.js` to ignore target directories in linting processes. * feat: integrate Tauri and QuickJS support in rust-core - Added `tauri` and `rquickjs` as optional dependencies in `Cargo.toml` to enable Tauri integration and JavaScript execution. - Introduced `CronScheduler` and `PingScheduler` modules for managing scheduled tasks and health checks for skills. - Implemented `MemoryState` struct for shared app-state management in the memory client. - Updated the runtime module to include new schedulers and ensure proper integration with Tauri features. - Enhanced the skill registry to support new functionalities related to skill management and communication. * chore: update ESLint configuration and refactor Rust module imports - Added 'rust-core/**' to ESLint ignore list to streamline linting processes. - Refactored import paths in Rust modules to directly reference the `memory` module, enhancing clarity and maintainability. - Improved formatting in JavaScript files for better readability and consistency. * refactor: rename rust-core to openhuman-core and update dependencies - Renamed the `rust-core` module to `openhuman-core` across all files for consistency. - Updated `Cargo.lock` to include `openhuman-core` as a new dependency and removed `rust-core`. - Adjusted import paths in the codebase to reflect the new module name, ensuring all references are updated. - Enhanced the Tauri integration by modifying dependencies in `src-tauri/Cargo.toml` to point to `openhuman-core`. * refactor: simplify platform detection using match statements - Replaced multiple if-else statements with match expressions in `current_platform`, `get_platform`, and `register` functions for improved readability and maintainability. - Removed unnecessary conditional compilation for Android and iOS in the `SocketManager` and `tauri_bridge` modules, streamlining the codebase. - Enhanced the `send_notification` function to handle platform checks more efficiently. * refactor: update platform detection to use std::env::consts - Replaced `cfg!(target_os = "os_name")` checks with `std::env::consts::OS` for improved clarity and consistency across the codebase. - Added `rppal` as an optional dependency in `Cargo.toml` for Raspberry Pi support. - Cleaned up platform-specific code in various modules, enhancing maintainability. * refactor: streamline platform-specific code and improve readability - Replaced conditional compilation with `std::env::consts::OS` checks in various modules to enhance clarity and maintainability. - Simplified platform detection logic in `available_disk_space_mb`, `ensure_arduino_cli`, and `open_in_brave` functions. - Updated `screenshot_command_exists` test to conditionally skip based on the operating system. - Cleaned up unnecessary `#[cfg]` attributes, focusing on a more consistent approach across the codebase. * feat: introduce comprehensive AI configuration and memory management - Added multiple configuration files for OpenHuman AI, including `AGENTS.md`, `BOOTSTRAP.md`, `CONSCIOUS_LOOP.md`, `IDENTITY.md`, `MEMORY.md`, `README.md`, `SOUL.md`, `TOOLS.md`, and `USER.md` to define agent roles, onboarding processes, identity, memory management, and tool capabilities. - Implemented an encryption layer for AI memory storage in `encryption.rs`, utilizing AES-256-GCM for secure data handling. - Updated `lib.rs` to include the new AI module structure, enhancing the overall architecture and maintainability of the codebase. * refactor: update dependencies and configuration for improved structure - Removed `android_logger` and related packages from `Cargo.lock` and `Cargo.toml`, streamlining the dependency list. - Adjusted the `APP_IDENTIFIER` constant in `config.rs` to reflect the new application identifier. - Updated resource paths in `tauri.conf.json` for better organization. - Enhanced platform-specific dependency management in `Cargo.toml` for clarity and maintainability. * fix(core): gate ai module behind tauri-host feature * refactor: update AI loading mechanisms and improve platform handling - Refactored the AI configuration loading in `loader.ts` and `tools/loader.ts` to utilize Tauri commands for desktop environments, enhancing performance and reliability. - Updated paths for AI markdown files to reflect the new `rust-core` structure. - Introduced a new end-to-end test for Tauri command interactions in `tauriCoreBridge.e2e.test.ts`. - Cleaned up platform-specific code across various modules, ensuring a more consistent approach to handling desktop and web contexts. * feat: add sidecar core binary build and staging for Tauri bundler - Implemented build steps for the sidecar core binary in multiple workflows, targeting both x86_64-unknown-linux-gnu and aarch64-apple-darwin architectures. - Added staging steps to copy the built binaries into the Tauri resources directory, ensuring proper integration for application bundling. - Updated relevant workflows to enhance the build process and streamline artifact management. * refactor: enhance AI loading logic for Tauri integration - Updated `loader.ts` and `tools/loader.ts` to prioritize Tauri commands for loading configurations in desktop environments, with a fallback to bundled markdown files for web contexts. - Adjusted test mocks to reflect the new file paths for tools markdown. - Improved test setup to mock Tauri API behavior accurately. * docs: update CLAUDE.md and remove Android build scripts from package.json - Added a new section in CLAUDE.md detailing the runtime scope, clarifying that Tauri is desktop-only and should not include mobile or web branches. - Removed Android development and build scripts from package.json to streamline the project for desktop platforms only. * refactor: streamline import statements and enhance test mock structure - Reordered import statements in `loader.ts` and `tools/loader.ts` for consistency. - Simplified mock implementation in `tauriCoreBridge.e2e.test.ts` to improve readability and maintainability. - Added external binary configuration in `build.rs` to support resource management during local builds. * test: stabilize core/unit test paths and tauri build-test config * chore: update subproject commit reference in skills * chore: update Tauri build configuration to include custom environment variable - Modified the Tauri build command in the GitHub Actions workflow to set a custom configuration for updater artifacts, enhancing the build process for the x86_64-unknown-linux-gnu target.
This commit is contained in:
@@ -72,7 +72,18 @@ jobs:
|
||||
- name: Install skills dependencies
|
||||
run: cd skills && yarn install --frozen-lockfile
|
||||
|
||||
- name: Build sidecar core binary
|
||||
run: cargo build --manifest-path rust-core/Cargo.toml --release --target x86_64-unknown-linux-gnu --bin openhuman-core
|
||||
|
||||
- name: Stage sidecar for Tauri bundler
|
||||
run: |
|
||||
mkdir -p src-tauri/binaries
|
||||
cp rust-core/target/x86_64-unknown-linux-gnu/release/openhuman-core src-tauri/binaries/openhuman-core-x86_64-unknown-linux-gnu
|
||||
chmod +x src-tauri/binaries/openhuman-core-x86_64-unknown-linux-gnu
|
||||
|
||||
- name: Build Tauri app
|
||||
run: yarn tauri build --bundles none -- --target x86_64-unknown-linux-gnu
|
||||
run: |
|
||||
TAURI_CONFIG_OVERRIDE='{"bundle":{"createUpdaterArtifacts":false}}'
|
||||
yarn tauri build -c "$TAURI_CONFIG_OVERRIDE" --bundles none -- --target x86_64-unknown-linux-gnu
|
||||
env:
|
||||
NODE_ENV: production
|
||||
|
||||
@@ -87,6 +87,23 @@ jobs:
|
||||
VITE_SENTRY_DSN: ${{ vars.VITE_SENTRY_DSN }}
|
||||
VITE_DEBUG: ${{ vars.VITE_DEBUG }}
|
||||
|
||||
- name: Build sidecar core binary
|
||||
shell: bash
|
||||
run: |
|
||||
cargo build \
|
||||
--manifest-path rust-core/Cargo.toml \
|
||||
--release \
|
||||
--target aarch64-apple-darwin \
|
||||
--bin openhuman-core
|
||||
|
||||
- name: Stage sidecar for Tauri bundler
|
||||
shell: bash
|
||||
run: |
|
||||
mkdir -p src-tauri/binaries
|
||||
cp rust-core/target/aarch64-apple-darwin/release/openhuman-core \
|
||||
src-tauri/binaries/openhuman-core-aarch64-apple-darwin
|
||||
chmod +x src-tauri/binaries/openhuman-core-aarch64-apple-darwin
|
||||
|
||||
- name: Build, package (no GitHub release upload)
|
||||
uses: tauri-apps/tauri-action@v0.6.2
|
||||
env:
|
||||
|
||||
@@ -167,8 +167,8 @@ jobs:
|
||||
args: '--target x86_64-apple-darwin'
|
||||
target: 'x86_64-apple-darwin'
|
||||
- platform: 'ubuntu-22.04'
|
||||
args: ''
|
||||
target: ''
|
||||
args: '--target x86_64-unknown-linux-gnu'
|
||||
target: 'x86_64-unknown-linux-gnu'
|
||||
runs-on: ${{ matrix.settings.platform }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
@@ -288,6 +288,28 @@ jobs:
|
||||
VITE_SENTRY_DSN: ${{ secrets.VITE_SENTRY_DSN }}
|
||||
VITE_DEBUG: ${{ vars.VITE_DEBUG }}
|
||||
|
||||
- name: Build sidecar core binary
|
||||
shell: bash
|
||||
run: |
|
||||
cargo build \
|
||||
--manifest-path rust-core/Cargo.toml \
|
||||
--release \
|
||||
--target "$MATRIX_TARGET" \
|
||||
--bin openhuman-core
|
||||
env:
|
||||
MATRIX_TARGET: ${{ matrix.settings.target }}
|
||||
|
||||
- name: Stage sidecar for Tauri bundler
|
||||
shell: bash
|
||||
run: |
|
||||
mkdir -p src-tauri/binaries
|
||||
SOURCE="rust-core/target/$MATRIX_TARGET/release/openhuman-core"
|
||||
DEST="src-tauri/binaries/openhuman-core-$MATRIX_TARGET"
|
||||
cp "$SOURCE" "$DEST"
|
||||
chmod +x "$DEST"
|
||||
env:
|
||||
MATRIX_TARGET: ${{ matrix.settings.target }}
|
||||
|
||||
- name: Build, package and publish
|
||||
uses: tauri-apps/tauri-action@v0.6.2
|
||||
id: build-tauri
|
||||
@@ -305,7 +327,7 @@ jobs:
|
||||
# macOS 10.15+ required for std::filesystem used by llama.cpp
|
||||
MACOSX_DEPLOYMENT_TARGET: ${{ matrix.settings.platform == 'macos-latest' && '10.15' || '' }}
|
||||
with:
|
||||
args: '-c ${{ steps.config-overrides.outputs.json }} ${{ matrix.settings.args }}'
|
||||
args: '-c ${{ steps.config-overrides.outputs.json }} ${{ matrix.settings.args }} -- -- --bin OpenHuman'
|
||||
includeDebug: ${{ needs.get-version.outputs.should-publish == '' && inputs.forceRelease != 'true' }}
|
||||
includeRelease: ${{ needs.get-version.outputs.should-publish != '' || inputs.forceRelease == 'true' }}
|
||||
# TDLib dylibs are now bundled natively via build.rs + tauri.conf.json macOS.frameworks
|
||||
|
||||
@@ -219,8 +219,8 @@ jobs:
|
||||
# target: x86_64-apple-darwin
|
||||
# artifact_suffix: x86_64-apple-darwin
|
||||
- platform: ubuntu-22.04
|
||||
args: ''
|
||||
target: ''
|
||||
args: --target x86_64-unknown-linux-gnu
|
||||
target: x86_64-unknown-linux-gnu
|
||||
artifact_suffix: ubuntu
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -305,6 +305,28 @@ jobs:
|
||||
VITE_SENTRY_DSN: ${{ vars.VITE_SENTRY_DSN }}
|
||||
VITE_DEBUG: ${{ vars.VITE_DEBUG }}
|
||||
|
||||
- name: Build sidecar core binary
|
||||
shell: bash
|
||||
run: |
|
||||
cargo build \
|
||||
--manifest-path rust-core/Cargo.toml \
|
||||
--release \
|
||||
--target "$MATRIX_TARGET" \
|
||||
--bin openhuman-core
|
||||
env:
|
||||
MATRIX_TARGET: ${{ matrix.settings.target }}
|
||||
|
||||
- name: Stage sidecar for Tauri bundler
|
||||
shell: bash
|
||||
run: |
|
||||
mkdir -p src-tauri/binaries
|
||||
SOURCE="rust-core/target/$MATRIX_TARGET/release/openhuman-core"
|
||||
DEST="src-tauri/binaries/openhuman-core-$MATRIX_TARGET"
|
||||
cp "$SOURCE" "$DEST"
|
||||
chmod +x "$DEST"
|
||||
env:
|
||||
MATRIX_TARGET: ${{ matrix.settings.target }}
|
||||
|
||||
- name: Build, package and upload to release
|
||||
uses: tauri-apps/tauri-action@v0.6.2
|
||||
id: tauri-build
|
||||
@@ -328,51 +350,43 @@ jobs:
|
||||
# the desktop UI app binary is packaged.
|
||||
# Yarn v1 strips one "--" layer when invoking scripts, hence the
|
||||
# double-separator when forwarding flags to Cargo.
|
||||
args: -c ${{ steps.config-overrides.outputs.json }} ${{ matrix.settings.args }}
|
||||
args: -c ${{ steps.config-overrides.outputs.json }} ${{ matrix.settings.args }} -- -- --bin OpenHuman
|
||||
includeDebug: false
|
||||
includeRelease: true
|
||||
releaseId: ${{ needs.create-release.outputs.release_id }}
|
||||
owner: tinyhumansai
|
||||
repo: openhuman
|
||||
|
||||
- name: Verify macOS app bundle excludes standalone core binary
|
||||
- name: Verify macOS app bundle sidecar layout
|
||||
if: matrix.settings.platform == 'macos-latest'
|
||||
shell: bash
|
||||
run: |
|
||||
APP_PATH="src-tauri/target/${{ matrix.settings.target }}/release/bundle/macos/OpenHuman.app"
|
||||
echo "Inspecting bundle at: $APP_PATH"
|
||||
ls -la "$APP_PATH/Contents/MacOS"
|
||||
ls -la "$APP_PATH/Contents/Resources" | grep openhuman-core || true
|
||||
if [ -f "$APP_PATH/Contents/MacOS/openhuman-core" ]; then
|
||||
echo "Unexpected standalone binary found inside app bundle: openhuman-core"
|
||||
echo "Unexpected standalone core binary found in MacOS dir"
|
||||
exit 1
|
||||
fi
|
||||
if ! ls "$APP_PATH/Contents/Resources"/openhuman-core-* >/dev/null 2>&1; then
|
||||
echo "Sidecar core binary missing from app resources"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Build standalone CLI binaries
|
||||
- name: Build standalone CLI binary
|
||||
shell: bash
|
||||
run: |
|
||||
# Temporarily inject [[bin]] entries that were removed from Cargo.toml
|
||||
# to prevent Tauri's bundler from trying to copy them into app bundles.
|
||||
printf '\n[[bin]]\nname = "openhuman-core"\npath = "src/standalone/openhuman-core.rs"\n\n[[bin]]\nname = "openhuman-cli"\npath = "src/standalone/openhuman-cli.rs"\n' >> src-tauri/Cargo.toml
|
||||
|
||||
CARGO_TARGET=""
|
||||
if [ -n "$MATRIX_TARGET" ]; then
|
||||
CARGO_TARGET="--target $MATRIX_TARGET"
|
||||
fi
|
||||
cargo build --manifest-path src-tauri/Cargo.toml --release --features standalone-bins $CARGO_TARGET --bin openhuman-core --bin openhuman-cli
|
||||
cargo build --manifest-path rust-core/Cargo.toml --release --target "$MATRIX_TARGET" --bin openhuman-cli
|
||||
env:
|
||||
MATRIX_TARGET: ${{ matrix.settings.target }}
|
||||
|
||||
- name: Resolve standalone CLI artifact paths
|
||||
- name: Resolve standalone CLI artifact path
|
||||
id: cli-paths
|
||||
shell: bash
|
||||
run: |
|
||||
if [ -n "$MATRIX_TARGET" ]; then
|
||||
BASE_DIR="src-tauri/target/$MATRIX_TARGET/release"
|
||||
else
|
||||
BASE_DIR="src-tauri/target/release"
|
||||
fi
|
||||
BASE_DIR="rust-core/target/$MATRIX_TARGET/release"
|
||||
echo "base_dir=$BASE_DIR" >> "$GITHUB_OUTPUT"
|
||||
echo "core_path=$BASE_DIR/openhuman-core" >> "$GITHUB_OUTPUT"
|
||||
echo "cli_path=$BASE_DIR/openhuman-cli" >> "$GITHUB_OUTPUT"
|
||||
env:
|
||||
MATRIX_TARGET: ${{ matrix.settings.target }}
|
||||
@@ -382,7 +396,6 @@ jobs:
|
||||
with:
|
||||
name: standalone-bins-${{ matrix.settings.platform }}-${{ matrix.settings.artifact_suffix }}
|
||||
path: |
|
||||
${{ steps.cli-paths.outputs.core_path }}
|
||||
${{ steps.cli-paths.outputs.cli_path }}
|
||||
|
||||
cleanup-failed-release:
|
||||
|
||||
@@ -51,3 +51,4 @@ coverage/
|
||||
|
||||
tauri.key
|
||||
tauri.key.pub
|
||||
/target/
|
||||
|
||||
@@ -2,6 +2,12 @@
|
||||
|
||||
Cross-platform crypto community communication platform built with **Tauri v2** (React 19 + Rust). Targets desktop (Windows, macOS) and mobile (Android, iOS). Features deep Telegram integration via MTProto, real-time Socket.io communication, V8-based skill execution engine, and an MCP (Model Context Protocol) tool system for AI-driven Telegram interactions.
|
||||
|
||||
## Runtime Scope
|
||||
|
||||
- Tauri host/runtime is desktop-only.
|
||||
- Always run and validate Tauri codepaths as desktop (`windows`, `macOS`, `linux`).
|
||||
- Do not add or maintain Android/iOS/web runtime branches inside `src-tauri`.
|
||||
|
||||
## App Theme & Design System
|
||||
|
||||
**Design Philosophy**: Premium, sophisticated crypto platform with calm, trustworthy aesthetic.
|
||||
|
||||
Generated
+11298
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,3 @@
|
||||
[workspace]
|
||||
members = ["rust-core", "src-tauri"]
|
||||
resolver = "2"
|
||||
@@ -22,9 +22,12 @@ export default [
|
||||
{
|
||||
ignores: [
|
||||
'node_modules/**',
|
||||
'target/**',
|
||||
'**/target/**',
|
||||
'dist/**',
|
||||
'coverage/**',
|
||||
'src-tauri/**',
|
||||
'rust-core/**',
|
||||
'skills/**',
|
||||
'references/**',
|
||||
'scripts/**',
|
||||
|
||||
@@ -20,8 +20,6 @@
|
||||
"macos:build:sign:release": "yarn macos:build:release:signed",
|
||||
"macos:run": "open 'src-tauri/target/debug/bundle/macos/OpenHuman.app'",
|
||||
"macos:dev": "yarn macos:build:debug && open 'src-tauri/target/debug/bundle/macos/OpenHuman.app'",
|
||||
"android:dev": "tauri android dev",
|
||||
"android:build": "tauri android build",
|
||||
"test": "vitest run --config test/vitest.config.ts",
|
||||
"test:unit": "vitest run --config test/vitest.config.ts",
|
||||
"test:unit:watch": "vitest --config test/vitest.config.ts",
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
[package]
|
||||
name = "openhuman-core"
|
||||
version = "0.49.16"
|
||||
edition = "2021"
|
||||
description = "OpenHuman core business logic and RPC server"
|
||||
autobins = true
|
||||
|
||||
[lib]
|
||||
name = "openhuman_core"
|
||||
crate-type = ["rlib"]
|
||||
|
||||
[dependencies]
|
||||
tauri = { version = "2.10", optional = true }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
reqwest = { version = "0.12", default-features = false, features = ["json", "blocking", "rustls-tls", "native-tls", "stream", "http2", "multipart", "socks"] }
|
||||
tokio = { version = "1", features = ["full", "sync"] }
|
||||
once_cell = "1.19"
|
||||
parking_lot = "0.12"
|
||||
log = "0.4"
|
||||
env_logger = "0.11"
|
||||
base64 = "0.22"
|
||||
aes-gcm = "0.10"
|
||||
argon2 = "0.5"
|
||||
rand = "0.9"
|
||||
dirs = "5"
|
||||
sha2 = "0.10"
|
||||
hmac = "0.12"
|
||||
uuid = { version = "1", features = ["v4"] }
|
||||
anyhow = "1.0"
|
||||
async-trait = "0.1"
|
||||
chacha20poly1305 = "0.10"
|
||||
hex = "0.4"
|
||||
tokio-util = { version = "0.7", features = ["rt"] }
|
||||
landlock = { version = "0.4", optional = true }
|
||||
tokio-tungstenite = { version = "0.24", features = ["rustls-tls-webpki-roots"] }
|
||||
futures = "0.3"
|
||||
rusqlite = { version = "0.37", features = ["bundled"] }
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
cron = "0.12"
|
||||
futures-util = "0.3"
|
||||
directories = "6"
|
||||
toml = "1.0"
|
||||
shellexpand = "3.1"
|
||||
schemars = "1.2"
|
||||
tracing = { version = "0.1", default-features = false }
|
||||
tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt", "ansi", "env-filter"] }
|
||||
prometheus = { version = "0.14", default-features = false }
|
||||
urlencoding = "2.1"
|
||||
thiserror = "2.0"
|
||||
ring = "0.17"
|
||||
prost = { version = "0.14", default-features = false }
|
||||
postgres = { version = "0.19", features = ["with-chrono-0_4"] }
|
||||
chrono-tz = "0.10"
|
||||
dialoguer = { version = "0.12", features = ["fuzzy-select"] }
|
||||
console = "0.16"
|
||||
glob = "0.3"
|
||||
regex = "1.10"
|
||||
hostname = "0.4.2"
|
||||
rustls = { version = "0.23", features = ["ring"] }
|
||||
rustls-pki-types = "1.14.0"
|
||||
tokio-rustls = "0.26.4"
|
||||
webpki-roots = "1.0.6"
|
||||
clap = { version = "4.5", features = ["derive"] }
|
||||
lettre = { version = "0.11.19", default-features = false, features = ["builder", "smtp-transport", "rustls-tls"] }
|
||||
mail-parser = "0.11.2"
|
||||
async-imap = { version = "0.11", features = ["runtime-tokio"], default-features = false }
|
||||
axum = { version = "0.8", default-features = false, features = ["http1", "json", "tokio", "query", "ws", "macros"] }
|
||||
tower = { version = "0.5", default-features = false }
|
||||
tower-http = { version = "0.6", default-features = false, features = ["limit", "timeout"] }
|
||||
http-body-util = "0.1"
|
||||
opentelemetry = { version = "0.31", default-features = false, features = ["trace", "metrics"] }
|
||||
opentelemetry_sdk = { version = "0.31", default-features = false, features = ["trace", "metrics"] }
|
||||
opentelemetry-otlp = { version = "0.31", default-features = false, features = ["trace", "metrics", "http-proto", "reqwest-client", "reqwest-rustls-webpki-roots"] }
|
||||
tokio-stream = { version = "0.1.18", features = ["full"] }
|
||||
url = "2"
|
||||
tinyhumansai = "0.1.6"
|
||||
rquickjs = { version = "0.11", features = ["futures", "macro", "loader", "parallel"], optional = true }
|
||||
|
||||
matrix-sdk = { version = "0.16", optional = true, default-features = false, features = ["e2e-encryption", "rustls-tls", "markdown"] }
|
||||
fantoccini = { version = "0.22.0", optional = true, default-features = false, features = ["rustls-tls"] }
|
||||
serde-big-array = { version = "0.5", optional = true }
|
||||
nusb = { version = "0.2", default-features = false, optional = true }
|
||||
tokio-serial = { version = "5", default-features = false, optional = true }
|
||||
rppal = { version = "0.22", optional = true }
|
||||
probe-rs = { version = "0.30", optional = true }
|
||||
pdf-extract = { version = "0.10", optional = true }
|
||||
wa-rs = { version = "0.2", optional = true, default-features = false }
|
||||
wa-rs-core = { version = "0.2", optional = true, default-features = false }
|
||||
wa-rs-binary = { version = "0.2", optional = true, default-features = false }
|
||||
wa-rs-proto = { version = "0.2", optional = true, default-features = false }
|
||||
wa-rs-ureq-http = { version = "0.2", optional = true }
|
||||
wa-rs-tokio-transport = { version = "0.2", optional = true, default-features = false }
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3"
|
||||
|
||||
[features]
|
||||
tauri-host = ["dep:tauri", "dep:rquickjs"]
|
||||
sandbox-landlock = ["dep:landlock"]
|
||||
sandbox-bubblewrap = []
|
||||
hardware = ["dep:nusb", "dep:tokio-serial"]
|
||||
channel-matrix = ["dep:matrix-sdk"]
|
||||
peripheral-rpi = ["dep:rppal"]
|
||||
browser-native = ["dep:fantoccini"]
|
||||
fantoccini = ["browser-native"]
|
||||
landlock = ["sandbox-landlock"]
|
||||
probe = ["dep:probe-rs"]
|
||||
rag-pdf = ["dep:pdf-extract"]
|
||||
whatsapp-web = ["dep:wa-rs", "dep:wa-rs-core", "dep:wa-rs-binary", "dep:wa-rs-proto", "dep:wa-rs-ureq-http", "dep:wa-rs-tokio-transport", "serde-big-array"]
|
||||
@@ -19,14 +19,14 @@ items that are:
|
||||
Return a JSON array of actionable items. Each item must have this exact structure:
|
||||
|
||||
{
|
||||
"title": "Short descriptive title (under 80 chars)",
|
||||
"description": "1-2 sentence explanation with context",
|
||||
"source": "email|calendar|telegram|ai_insight|system|trading|security",
|
||||
"priority": "critical|important|normal",
|
||||
"actionable": true,
|
||||
"requires_confirmation": false,
|
||||
"has_complex_action": false,
|
||||
"source_label": "Human-readable source name (e.g. Gmail, Telegram, Notion)"
|
||||
"title": "Short descriptive title (under 80 chars)",
|
||||
"description": "1-2 sentence explanation with context",
|
||||
"source": "email|calendar|telegram|ai_insight|system|trading|security",
|
||||
"priority": "critical|important|normal",
|
||||
"actionable": true,
|
||||
"requires_confirmation": false,
|
||||
"has_complex_action": false,
|
||||
"source_label": "Human-readable source name (e.g. Gmail, Telegram, Notion)"
|
||||
}
|
||||
|
||||
## Rules
|
||||
@@ -310,24 +310,24 @@ fn execute(cli: Cli) -> Result<serde_json::Value, String> {
|
||||
}
|
||||
}
|
||||
|
||||
fn load_config() -> Result<openhuman::openhuman::config::Config, String> {
|
||||
fn load_config() -> Result<openhuman_core::openhuman::config::Config, String> {
|
||||
let runtime = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.map_err(|e| format!("failed to build runtime: {e}"))?;
|
||||
runtime
|
||||
.block_on(openhuman::openhuman::config::Config::load_or_init())
|
||||
.block_on(openhuman_core::openhuman::config::Config::load_or_init())
|
||||
.map_err(|e| format!("failed to load config: {e}"))
|
||||
}
|
||||
|
||||
fn status_value(
|
||||
status: openhuman::openhuman::service::ServiceStatus,
|
||||
status: openhuman_core::openhuman::service::ServiceStatus,
|
||||
) -> Result<serde_json::Value, String> {
|
||||
serde_json::to_value(status).map_err(|e| format!("failed to serialize service status: {e}"))
|
||||
}
|
||||
|
||||
fn command_response_json(
|
||||
status: openhuman::openhuman::service::ServiceStatus,
|
||||
status: openhuman_core::openhuman::service::ServiceStatus,
|
||||
log: &str,
|
||||
) -> Result<serde_json::Value, String> {
|
||||
Ok(json!({
|
||||
@@ -338,35 +338,35 @@ fn command_response_json(
|
||||
|
||||
fn local_service_install() -> Result<serde_json::Value, String> {
|
||||
let config = load_config()?;
|
||||
let status = openhuman::openhuman::service::install(&config)
|
||||
let status = openhuman_core::openhuman::service::install(&config)
|
||||
.map_err(|e| format!("service install failed: {e}"))?;
|
||||
command_response_json(status, "service install completed")
|
||||
}
|
||||
|
||||
fn local_service_start() -> Result<serde_json::Value, String> {
|
||||
let config = load_config()?;
|
||||
let status = openhuman::openhuman::service::start(&config)
|
||||
let status = openhuman_core::openhuman::service::start(&config)
|
||||
.map_err(|e| format!("service start failed: {e}"))?;
|
||||
command_response_json(status, "service start completed")
|
||||
}
|
||||
|
||||
fn local_service_stop() -> Result<serde_json::Value, String> {
|
||||
let config = load_config()?;
|
||||
let status = openhuman::openhuman::service::stop(&config)
|
||||
let status = openhuman_core::openhuman::service::stop(&config)
|
||||
.map_err(|e| format!("service stop failed: {e}"))?;
|
||||
command_response_json(status, "service stop completed")
|
||||
}
|
||||
|
||||
fn local_service_status() -> Result<serde_json::Value, String> {
|
||||
let config = load_config()?;
|
||||
let status = openhuman::openhuman::service::status(&config)
|
||||
let status = openhuman_core::openhuman::service::status(&config)
|
||||
.map_err(|e| format!("service status failed: {e}"))?;
|
||||
command_response_json(status, "service status fetched")
|
||||
}
|
||||
|
||||
fn local_service_uninstall() -> Result<serde_json::Value, String> {
|
||||
let config = load_config()?;
|
||||
let status = openhuman::openhuman::service::uninstall(&config)
|
||||
let status = openhuman_core::openhuman::service::uninstall(&config)
|
||||
.map_err(|e| format!("service uninstall failed: {e}"))?;
|
||||
command_response_json(status, "service uninstall completed")
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
fn main() {
|
||||
let args: Vec<String> = std::env::args().skip(1).collect();
|
||||
if let Err(err) = openhuman::core_server::run_from_cli_args(&args) {
|
||||
if let Err(err) = openhuman_core::core_server::run_from_cli_args(&args) {
|
||||
eprintln!("openhuman-core failed: {err}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
#[cfg(feature = "tauri-host")]
|
||||
pub mod ai;
|
||||
pub mod auth;
|
||||
pub mod core_server;
|
||||
pub mod memory;
|
||||
pub mod models;
|
||||
pub mod openhuman;
|
||||
pub mod runtime;
|
||||
|
||||
pub fn run_core_from_args(args: &[String]) -> anyhow::Result<()> {
|
||||
core_server::run_from_cli_args(args)
|
||||
}
|
||||
@@ -0,0 +1,453 @@
|
||||
//! TinyHumans Neocortex persistent memory layer.
|
||||
//!
|
||||
//! Wraps `TinyHumanMemoryClient` with helpers for skill data-sync.
|
||||
//! The client is initialised at runtime with the user's JWT token (from Redux
|
||||
//! `authSlice.token`) via the `init_memory_client` Tauri command, not from env vars.
|
||||
|
||||
use std::sync::Arc;
|
||||
use tinyhumansai::{
|
||||
DeleteMemoryParams, InsertMemoryParams, Priority, QueryMemoryParams, RecallMemoryParams,
|
||||
SourceType, TinyHumanConfig, TinyHumansMemoryClient,
|
||||
};
|
||||
use uuid::Uuid;
|
||||
|
||||
/// Shared, cloneable handle to the memory client.
|
||||
pub type MemoryClientRef = Arc<MemoryClient>;
|
||||
|
||||
/// Shared app-state slot for the memory client.
|
||||
pub struct MemoryState(pub std::sync::Mutex<Option<MemoryClientRef>>);
|
||||
|
||||
pub struct MemoryClient {
|
||||
inner: TinyHumansMemoryClient,
|
||||
}
|
||||
|
||||
impl MemoryClient {
|
||||
/// Construct from a JWT token (sourced from `authSlice.token` in the Redux store).
|
||||
/// Returns `None` if the token is empty or client construction fails.
|
||||
pub fn from_token(jwt_token: String) -> Option<Self> {
|
||||
log::info!(
|
||||
"[memory] from_token: entry (token_len={})",
|
||||
jwt_token.trim().len()
|
||||
);
|
||||
if jwt_token.trim().is_empty() {
|
||||
log::warn!("[memory] from_token: exit — token is empty, returning None");
|
||||
return None;
|
||||
}
|
||||
let config = if let Ok(base_url) =
|
||||
std::env::var("OPENHUMAN_BASE_URL").or_else(|_| std::env::var("TINYHUMANS_BASE_URL"))
|
||||
{
|
||||
log::info!(
|
||||
"[memory] from_token: constructing client (base_url={base_url}, source=memory_env)"
|
||||
);
|
||||
TinyHumanConfig::new(jwt_token).with_base_url(base_url)
|
||||
} else {
|
||||
let backend_url = std::env::var("VITE_BACKEND_URL")
|
||||
.ok()
|
||||
.filter(|url| !url.trim().is_empty())
|
||||
.unwrap_or_else(|| "http://localhost:5005".to_string());
|
||||
log::info!(
|
||||
"[memory] from_token: constructing client (base_url={backend_url}, source=fallback_env_default)"
|
||||
);
|
||||
TinyHumanConfig::new(jwt_token).with_base_url(backend_url)
|
||||
};
|
||||
match TinyHumansMemoryClient::new(config) {
|
||||
Ok(inner) => {
|
||||
log::info!("[memory] from_token: exit — client created successfully");
|
||||
Some(Self { inner })
|
||||
}
|
||||
Err(e) => {
|
||||
log::warn!("[memory] from_token: exit — client construction failed: {e}");
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Store a skill data-sync result.
|
||||
///
|
||||
/// Inserts the document then polls `ingestion_job_status` every 30 s until
|
||||
/// the job reaches `completed` (or `failed`/`error`). Returns only after the
|
||||
/// ingestion job is confirmed complete.
|
||||
pub async fn store_skill_sync(
|
||||
&self,
|
||||
skill_id: &str,
|
||||
integration_id: &str,
|
||||
title: &str,
|
||||
content: &str,
|
||||
source_type: Option<SourceType>,
|
||||
metadata: Option<serde_json::Value>,
|
||||
priority: Option<Priority>,
|
||||
created_at: Option<f64>,
|
||||
updated_at: Option<f64>,
|
||||
document_id: Option<String>,
|
||||
) -> Result<(), String> {
|
||||
let namespace = skill_id.to_string();
|
||||
log::info!(
|
||||
"[memory] store_skill_sync: entry (namespace={namespace}, title={title:?}, content_len={})",
|
||||
content.len()
|
||||
);
|
||||
|
||||
let document_id_final = document_id.unwrap_or_else(|| Uuid::new_v4().to_string());
|
||||
|
||||
log::info!(
|
||||
"[memory] insert_memory: calling SDK (namespace={namespace}, title={title:?}), content_len={}",
|
||||
content.len()
|
||||
);
|
||||
|
||||
let insert_resp = self
|
||||
.inner
|
||||
.insert_memory(InsertMemoryParams {
|
||||
document_id: document_id_final,
|
||||
title: title.to_string(),
|
||||
content: content.to_string(),
|
||||
namespace: namespace.clone(),
|
||||
source_type,
|
||||
metadata,
|
||||
priority,
|
||||
created_at,
|
||||
updated_at,
|
||||
..Default::default()
|
||||
})
|
||||
.await
|
||||
.map_err(|e| {
|
||||
log::warn!(
|
||||
"[memory] insert_memory: SDK error — kind={:?} msg={e}",
|
||||
classify_insert_error(&e)
|
||||
);
|
||||
format!("Memory insert failed: {e}")
|
||||
})?;
|
||||
|
||||
log::info!(
|
||||
"[memory] insert_memory: accepted (namespace={namespace}, status={:?}, job_id={:?})",
|
||||
insert_resp.data.status,
|
||||
insert_resp.data.job_id
|
||||
);
|
||||
|
||||
// If the API returned a job_id, poll until the job completes.
|
||||
if let Some(job_id) = insert_resp.data.job_id {
|
||||
log::info!("[memory] ingestion job queued (job_id={job_id}), polling every 30s...");
|
||||
|
||||
loop {
|
||||
tokio::time::sleep(std::time::Duration::from_secs(30)).await;
|
||||
|
||||
match self.inner.get_ingestion_job(&job_id).await {
|
||||
Ok(status_resp) => {
|
||||
let state = status_resp.data.state.as_deref().unwrap_or("unknown");
|
||||
|
||||
log::info!(
|
||||
"[memory] ingestion job status: job_id={job_id}, state={state}, \
|
||||
attempts={:?}, completed_at={:?}",
|
||||
status_resp.data.attempts,
|
||||
status_resp.data.completed_at
|
||||
);
|
||||
|
||||
match state {
|
||||
"completed" => {
|
||||
log::info!(
|
||||
"[memory] ingestion job completed (job_id={job_id}, namespace={namespace})"
|
||||
);
|
||||
break;
|
||||
}
|
||||
"failed" | "error" => {
|
||||
let err_msg = status_resp
|
||||
.data
|
||||
.error
|
||||
.unwrap_or_else(|| format!("job state={state}"));
|
||||
log::warn!(
|
||||
"[memory] ingestion job failed: job_id={job_id}, error={err_msg}"
|
||||
);
|
||||
log::warn!(
|
||||
"[memory] store_skill_sync: exit — ingestion failed (namespace={namespace})"
|
||||
);
|
||||
return Err(format!("Ingestion job failed: {err_msg}"));
|
||||
}
|
||||
_ => {
|
||||
// pending / processing / queued — keep waiting
|
||||
log::info!(
|
||||
"[memory] ingestion job still in progress (state={state}), waiting 30s..."
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
log::warn!(
|
||||
"[memory] ingestion job status poll error (job_id={job_id}): {e} — retrying in 30s"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log::info!("[memory] no job_id returned — insert assumed synchronous, proceeding");
|
||||
}
|
||||
|
||||
log::info!("[memory] store_skill_sync: exit — ok (namespace={namespace})");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Query relevant context for a skill integration (RAG).
|
||||
pub async fn query_skill_context(
|
||||
&self,
|
||||
skill_id: &str,
|
||||
_integration_id: &str,
|
||||
query: &str,
|
||||
max_chunks: u32,
|
||||
) -> Result<String, String> {
|
||||
let namespace = skill_id.to_string();
|
||||
log::info!("[memory] query_skill_context: entry (namespace={namespace}, max_chunks={max_chunks}, query={query:?})");
|
||||
log::debug!(
|
||||
"[memory] query_skill_context: payload → namespace={namespace} | max_chunks={max_chunks} | query={query}"
|
||||
);
|
||||
let res = self
|
||||
.inner
|
||||
.query_memory(QueryMemoryParams {
|
||||
query: query.to_string(),
|
||||
namespace: Some(namespace.clone()),
|
||||
max_chunks: Some(f64::from(max_chunks)),
|
||||
..Default::default()
|
||||
})
|
||||
.await
|
||||
.map_err(|e| {
|
||||
log::warn!(
|
||||
"[memory] query_skill_context: exit — error (namespace={namespace}): {e}"
|
||||
);
|
||||
format!("Memory query failed: {e}")
|
||||
})?;
|
||||
let response = res.data.response.unwrap_or_default();
|
||||
log::info!(
|
||||
"[memory] query_skill_context: exit — ok (namespace={namespace}, response_len={})",
|
||||
response.len()
|
||||
);
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
/// Recall context from the Master memory node for a given namespace.
|
||||
/// Returns the synthesised `response` string, or `None` if the server returned nothing.
|
||||
pub async fn recall_skill_context(
|
||||
&self,
|
||||
skill_id: &str,
|
||||
_integration_id: &str,
|
||||
max_chunks: u32,
|
||||
) -> Result<Option<serde_json::Value>, String> {
|
||||
let namespace = skill_id.to_string();
|
||||
log::info!(
|
||||
"[memory] recall_skill_context: entry (namespace={namespace}, max_chunks={max_chunks})"
|
||||
);
|
||||
let res = self
|
||||
.inner
|
||||
.recall_memory(RecallMemoryParams {
|
||||
namespace: Some(namespace.clone()),
|
||||
max_chunks: Some(f64::from(max_chunks)),
|
||||
})
|
||||
.await
|
||||
.map_err(|e| {
|
||||
log::warn!(
|
||||
"[memory] recall_skill_context: exit — error (namespace={namespace}): {e}"
|
||||
);
|
||||
format!("Memory recall failed: {e}")
|
||||
})?;
|
||||
let response = res.data.context;
|
||||
log::info!(
|
||||
"[memory] recall_skill_context: exit — ok (namespace={namespace}, has_response={})",
|
||||
response.is_some()
|
||||
);
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
/// List all ingested memory documents as returned by the API.
|
||||
pub async fn list_documents(&self) -> Result<serde_json::Value, String> {
|
||||
self.inner
|
||||
.list_documents(tinyhumansai::ListDocumentsParams::default())
|
||||
.await
|
||||
.map_err(|e| format!("Memory list documents failed: {e}"))
|
||||
}
|
||||
|
||||
/// Delete a specific document from a namespace.
|
||||
pub async fn delete_document(
|
||||
&self,
|
||||
document_id: &str,
|
||||
namespace: &str,
|
||||
) -> Result<serde_json::Value, String> {
|
||||
self.inner
|
||||
.delete_document(document_id, namespace)
|
||||
.await
|
||||
.map_err(|e| format!("Memory delete document failed: {e}"))
|
||||
}
|
||||
|
||||
/// Query memory context by namespace directly.
|
||||
pub async fn query_namespace_context(
|
||||
&self,
|
||||
namespace: &str,
|
||||
query: &str,
|
||||
max_chunks: u32,
|
||||
) -> Result<String, String> {
|
||||
let res = self
|
||||
.inner
|
||||
.query_memory(QueryMemoryParams {
|
||||
query: query.to_string(),
|
||||
namespace: Some(namespace.to_string()),
|
||||
max_chunks: Some(f64::from(max_chunks)),
|
||||
..Default::default()
|
||||
})
|
||||
.await
|
||||
.map_err(|e| format!("Memory query failed: {e}"))?;
|
||||
Ok(res.data.response.unwrap_or_default())
|
||||
}
|
||||
|
||||
/// Recall memory context by namespace directly.
|
||||
pub async fn recall_namespace_context(
|
||||
&self,
|
||||
namespace: &str,
|
||||
max_chunks: u32,
|
||||
) -> Result<Option<String>, String> {
|
||||
let res = self
|
||||
.inner
|
||||
.recall_memory(RecallMemoryParams {
|
||||
namespace: Some(namespace.to_string()),
|
||||
max_chunks: Some(f64::from(max_chunks)),
|
||||
})
|
||||
.await
|
||||
.map_err(|e| format!("Memory recall failed: {e}"))?;
|
||||
Ok(res.data.response)
|
||||
}
|
||||
|
||||
/// Delete all memories for a skill integration (e.g. on OAuth revoke).
|
||||
pub async fn clear_skill_memory(
|
||||
&self,
|
||||
skill_id: &str,
|
||||
_integration_id: &str,
|
||||
) -> Result<(), String> {
|
||||
let namespace = skill_id.to_string();
|
||||
log::info!("[memory] clear_skill_memory: entry (namespace={namespace})");
|
||||
log::debug!("[memory] clear_skill_memory: payload → namespace={namespace}");
|
||||
let result = self
|
||||
.inner
|
||||
.delete_memory(DeleteMemoryParams {
|
||||
namespace: Some(namespace.clone()),
|
||||
})
|
||||
.await
|
||||
.map(|_| ())
|
||||
.map_err(|e| format!("Memory delete failed: {e}"));
|
||||
match &result {
|
||||
Ok(()) => log::info!("[memory] clear_skill_memory: exit — ok (namespace={namespace})"),
|
||||
Err(e) => {
|
||||
log::warn!("[memory] clear_skill_memory: exit — error (namespace={namespace}): {e}")
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
fn classify_insert_error(e: &tinyhumansai::TinyHumansError) -> &'static str {
|
||||
let msg = e.to_string();
|
||||
if msg.contains("dns") || msg.contains("resolve") || msg.contains("lookup") {
|
||||
"dns_failure"
|
||||
} else if msg.contains("tls") || msg.contains("certificate") || msg.contains("ssl") {
|
||||
"tls_failure"
|
||||
} else if msg.contains("Connection refused") || msg.contains("connection refused") {
|
||||
"connection_refused"
|
||||
} else if msg.contains("timed out") || msg.contains("deadline") {
|
||||
"timeout"
|
||||
} else if msg.contains("error sending request") {
|
||||
"transport_error"
|
||||
} else {
|
||||
"other"
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
/// Integration test against the real TinyHumans production API.
|
||||
///
|
||||
/// Verifies: JWT is accepted, endpoint is reachable, and request shape is correct.
|
||||
/// A `400 Insufficient ingestion budget` response is treated as a PASS because it proves:
|
||||
/// - auth succeeded (not 401/403)
|
||||
/// - the endpoint and payload are correctly formed (not 422)
|
||||
/// - the account quota is the only limiting factor
|
||||
///
|
||||
/// Run with:
|
||||
/// JWT_TOKEN=<your-openhuman-jwt> \
|
||||
/// cargo test --manifest-path src-tauri/Cargo.toml test_memory_skill_sync_flow -- --ignored --nocapture
|
||||
#[tokio::test]
|
||||
#[ignore]
|
||||
async fn test_memory_skill_sync_flow() {
|
||||
let jwt_token = std::env::var("JWT_TOKEN").expect("JWT_TOKEN must be set");
|
||||
|
||||
let client = MemoryClient::from_token(jwt_token).expect("client creation failed");
|
||||
|
||||
let skill_id = "gmail";
|
||||
let integration_id = "test@openhuman.dev";
|
||||
|
||||
let dummy_content = serde_json::json!({
|
||||
"integrationId": integration_id,
|
||||
"type": "gmail_sync",
|
||||
"summary": "Synced 45 emails from inbox",
|
||||
"labels": ["Work", "Personal", "Finance"],
|
||||
"recentSenders": ["alice@example.com", "bob@example.com"],
|
||||
"unreadCount": 12,
|
||||
"syncedAt": "2026-03-17T12:00:00Z"
|
||||
});
|
||||
|
||||
// ── 1. Insert ─────────────────────────────────────────────────────────
|
||||
let insert_result = client
|
||||
.store_skill_sync(
|
||||
skill_id,
|
||||
integration_id,
|
||||
"Gmail OAuth sync — test@openhuman.dev",
|
||||
&serde_json::to_string_pretty(&dummy_content).unwrap(),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
|
||||
println!("[test] insert result: {insert_result:?}");
|
||||
|
||||
match &insert_result {
|
||||
Ok(()) => {
|
||||
println!("[test] ✓ INSERT succeeded — quota available");
|
||||
|
||||
// ── 2. Query ─────────────────────────────────────────────────
|
||||
let context = client
|
||||
.query_skill_context(
|
||||
skill_id,
|
||||
integration_id,
|
||||
"What emails were recently synced and who sent them?",
|
||||
10,
|
||||
)
|
||||
.await;
|
||||
println!("[test] query result: {context:?}");
|
||||
assert!(context.is_ok(), "query_memory failed: {context:?}");
|
||||
println!("[test] memory context:\n{}", context.unwrap());
|
||||
|
||||
// ── 3. Cleanup ────────────────────────────────────────────────
|
||||
let del = client.clear_skill_memory(skill_id, integration_id).await;
|
||||
println!("[test] delete result: {del:?}");
|
||||
assert!(del.is_ok(), "delete_memory failed: {del:?}");
|
||||
}
|
||||
Err(e) if e.contains("Insufficient ingestion budget") => {
|
||||
// Account quota exhausted — auth + endpoint + payload all correct.
|
||||
println!(
|
||||
"[test] ✓ API REACHABLE — auth accepted, endpoint correct.\n\
|
||||
Quota limited: {e}\n\
|
||||
Integration is wired correctly; upgrade the TinyHumans account \
|
||||
to enable full insert/query/delete flow."
|
||||
);
|
||||
// Not a code failure — pass the test.
|
||||
}
|
||||
Err(e) => {
|
||||
panic!("Unexpected error (not a quota issue): {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Smoke test: from_token() returns None for an empty token.
|
||||
#[test]
|
||||
fn test_from_token_returns_none_for_empty_token() {
|
||||
assert!(MemoryClient::from_token(String::new()).is_none());
|
||||
assert!(MemoryClient::from_token(" ".to_string()).is_none());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
pub mod auth;
|
||||
pub mod socket;
|
||||
+1
-1
@@ -247,7 +247,7 @@ end tell"#
|
||||
}
|
||||
|
||||
async fn health_check(&self) -> bool {
|
||||
if !cfg!(target_os = "macos") {
|
||||
if std::env::consts::OS != "macos" {
|
||||
return false;
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user