mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-28 21:44:38 +00:00
ci: speed up GitHub Actions builds (~14m → ~3-5m warm) (#136)
* chore: add CI profile for faster compilation in Cargo.toml files - Introduced a new `[profile.ci]` section in both root and Tauri Cargo.toml files to optimize build settings for continuous integration. - Adjusted compilation parameters to prioritize speed over runtime performance, including reduced optimization level and enabled code generation units. * refactor(tests): update agent test setup to return temporary directory - Modified the `build_agent_with` function calls in the agent tests to return a temporary directory alongside the agent instance, improving resource management during tests. - Ensured consistency in test setup across multiple test functions. * chore: update .gitignore to include fastembed_cache - Added 'workflow' and '.fastembed_cache' to the .gitignore file to prevent unnecessary files from being tracked in the repository. * test: enhance dispatch routing tests with panic handling Updated the `dispatch_routes_memory_doc_ingest` test to use `AssertUnwindSafe` and `catch_unwind` for better handling of potential panics during execution. This ensures that the test verifies route existence even if the handler encounters a panic, improving robustness against shared state issues in concurrent tests. * ci: speed up builds with rust-cache, sccache, mold linker, and CI profile - Replace manual Cargo registry cache with Swatinem/rust-cache@v2 (caches target/ directories for both core and Tauri crates) - Add mozilla-actions/sccache for cross-branch compilation caching - Install mold linker on Linux for faster linking - Use --profile ci for sidecar build (opt-level 1, codegen-units 16) - Override release profile env vars for Tauri build with CI-tuned settings - Add --bundles none to CI build (skip unused deb/appimage packaging) - Restrict push triggers to main branch only (PRs already cover feature branches, preventing duplicate runs) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(ci): unset RUSTC_WRAPPER for cargo fmt to avoid sccache errors The sccache-action sets RUSTC_WRAPPER globally for the job. cargo fmt invokes rustc through sccache which fails if the GHA cache service is unavailable. Clear RUSTC_WRAPPER for fmt steps and remove redundant per-step env overrides (the action already sets them job-wide). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * refactor: implement Default trait for various structs and enums - Added Default implementations for ConnectionStatus, AgentBuilder, NativeRuntime, AutocompleteEngine, CliChannel, ActionTracker, SkillStatus, and ExtractionMode to streamline object initialization. - Simplified condition checks in several places by replacing map_or with is_some_and and is_none_or for better readability and performance. - Updated various instances of string handling in message sending to remove unnecessary conversions. This refactor enhances code clarity and consistency across the codebase. * style: clean up whitespace and improve code formatting - Removed unnecessary blank lines in several files to enhance code readability. - Simplified condition checks by consolidating method calls into single lines for better clarity. - Improved formatting in various functions to maintain consistency across the codebase. * fix(ci): use --bundles deb instead of unsupported none value Tauri CLI on this version doesn't support 'none' as a bundle type. Use 'deb' as the lightest single bundle to minimize packaging time. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(ci): add Dockerfile and CI workflows for building and pushing Docker images - Introduced a Dockerfile to set up a CI environment with necessary dependencies for Tauri, Rust, Node.js, and sccache. - Created a new workflow to build and push the CI Docker image to GitHub Container Registry on main branch pushes. - Updated existing workflows to utilize the new Docker image for building and testing, enhancing consistency and efficiency in CI processes. * chore(ci): update container image references in CI workflows - Removed unnecessary permissions for packages in build, test, and typecheck workflows. - Updated container image references to use a specific digest instead of the latest tag for improved stability and reproducibility in CI processes. * fix(ci): use correct amd64 Docker image digest Previous digest was from arm64 build (Mac). Rebuilt with --platform linux/amd64 for GitHub Actions runners. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(ci): use tag instead of digest for Docker image reference GHCR doesn't support pulling OCI index manifests by digest reliably. Use the rust-1.93.0 tag which is pinned to a specific Rust version and resolves correctly on amd64 CI runners. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(ci): rename Docker package to openhuman_ci Move from nested ghcr.io/tinyhumansai/openhuman/ci-runner to ghcr.io/tinyhumansai/openhuman_ci to avoid GHCR nested package manifest resolution issues. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(ci): remove sccache env vars from container jobs sccache can't access the GHA cache API from inside a Docker container (missing ACTIONS_CACHE_URL/ACTIONS_RUNTIME_TOKEN). Swatinem/rust-cache already caches target/ which provides the main build speedup. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * chore: update installer smoke workflow to trigger on main branch pushes - Added a trigger for the installer smoke workflow to run on pushes to the main branch, enhancing CI coverage for mainline changes. * fix: enhance Sentry DSN retrieval logic - Updated the Sentry DSN retrieval process to include an additional fallback option using `option_env!`, ensuring that the DSN can be sourced from both environment variables and optional configuration, improving robustness in observability setup. * chore: add OPENHUMAN_SENTRY_DSN to release workflow and example secrets - Included the OPENHUMAN_SENTRY_DSN variable in the release workflow configuration to enhance observability setup. - Updated the ci-secrets.example.json file to include a placeholder for OPENHUMAN_SENTRY_DSN, providing clarity for developers on required environment variables. --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
8486a3b02e
commit
c5e5ae170c
@@ -0,0 +1,43 @@
|
||||
FROM ubuntu:22.04
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
# System deps for Tauri + mold linker + clang (for mold)
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
build-essential \
|
||||
curl \
|
||||
ca-certificates \
|
||||
git \
|
||||
pkg-config \
|
||||
libgtk-3-dev \
|
||||
libwebkit2gtk-4.1-dev \
|
||||
libappindicator3-dev \
|
||||
librsvg2-dev \
|
||||
patchelf \
|
||||
mold \
|
||||
clang \
|
||||
libssl-dev \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Rust 1.93.0 with minimal profile + fmt/clippy
|
||||
ENV RUSTUP_HOME=/usr/local/rustup \
|
||||
CARGO_HOME=/usr/local/cargo \
|
||||
PATH="/usr/local/cargo/bin:$PATH"
|
||||
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- \
|
||||
-y --default-toolchain 1.93.0 --profile minimal \
|
||||
-c rustfmt -c clippy \
|
||||
-t x86_64-unknown-linux-gnu
|
||||
|
||||
# Node.js 24.x + yarn
|
||||
RUN curl -fsSL https://deb.nodesource.com/setup_24.x | bash - \
|
||||
&& apt-get install -y --no-install-recommends nodejs \
|
||||
&& npm install -g yarn \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# sccache (pre-installed so the action only needs to configure it)
|
||||
RUN curl -fsSL https://github.com/mozilla/sccache/releases/download/v0.10.0/sccache-v0.10.0-x86_64-unknown-linux-musl.tar.gz \
|
||||
| tar xz -C /usr/local/bin --strip-components=1 sccache-v0.10.0-x86_64-unknown-linux-musl/sccache \
|
||||
&& chmod +x /usr/local/bin/sccache
|
||||
|
||||
# Verify installs
|
||||
RUN rustc --version && cargo --version && node --version && yarn --version && mold --version && sccache --version
|
||||
+17
-34
@@ -2,6 +2,7 @@ name: Build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
|
||||
permissions:
|
||||
@@ -16,6 +17,8 @@ jobs:
|
||||
build:
|
||||
name: Build Tauri App
|
||||
runs-on: ubuntu-22.04
|
||||
container:
|
||||
image: ghcr.io/tinyhumansai/openhuman_ci:rust-1.93.0
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
@@ -23,38 +26,13 @@ jobs:
|
||||
fetch-depth: 1
|
||||
submodules: true
|
||||
|
||||
- name: Setup Node.js 24.x
|
||||
uses: actions/setup-node@v4
|
||||
- name: Cache Rust build artifacts
|
||||
uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
node-version: 24.x
|
||||
cache: "yarn"
|
||||
|
||||
- name: Install Rust (rust-toolchain.toml)
|
||||
uses: dtolnay/rust-toolchain@1.93.0
|
||||
with:
|
||||
targets: x86_64-unknown-linux-gnu
|
||||
|
||||
- name: Install Tauri dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf
|
||||
|
||||
# Skip first 7 lines of Cargo.lock (workspace package version bumps) so the key tracks dependency changes only
|
||||
- name: Cargo.lock fingerprint (deps only)
|
||||
id: cargo-lock-fingerprint
|
||||
shell: bash
|
||||
run: |
|
||||
echo "hash=$(tail -n +8 Cargo.lock | openssl dgst -sha256 | awk '{print $2}')" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Cache Cargo registry and git sources
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
key: ${{ runner.os }}-cargo-registry-${{ steps.cargo-lock-fingerprint.outputs.hash }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-cargo-registry-
|
||||
workspaces: |
|
||||
. -> target
|
||||
app/src-tauri -> target
|
||||
cache-on-failure: true
|
||||
|
||||
- name: Cache node modules
|
||||
id: yarn-cache
|
||||
@@ -70,18 +48,23 @@ jobs:
|
||||
run: yarn install --frozen-lockfile
|
||||
|
||||
- name: Build sidecar core binary
|
||||
run: cargo build --manifest-path Cargo.toml --release --target x86_64-unknown-linux-gnu --bin openhuman-core
|
||||
run: cargo build --profile ci --target x86_64-unknown-linux-gnu --bin openhuman-core
|
||||
|
||||
- name: Stage sidecar for Tauri bundler
|
||||
run: |
|
||||
mkdir -p app/src-tauri/binaries
|
||||
cp target/x86_64-unknown-linux-gnu/release/openhuman-core app/src-tauri/binaries/openhuman-core-x86_64-unknown-linux-gnu
|
||||
cp target/x86_64-unknown-linux-gnu/ci/openhuman-core app/src-tauri/binaries/openhuman-core-x86_64-unknown-linux-gnu
|
||||
chmod +x app/src-tauri/binaries/openhuman-core-x86_64-unknown-linux-gnu
|
||||
|
||||
- name: Build Tauri app
|
||||
working-directory: app
|
||||
run: |
|
||||
TAURI_CONFIG_OVERRIDE='{"plugins":{"updater":{"active":false}}}'
|
||||
yarn tauri build -c "$TAURI_CONFIG_OVERRIDE"
|
||||
yarn tauri build -c "$TAURI_CONFIG_OVERRIDE" --bundles deb
|
||||
env:
|
||||
NODE_ENV: production
|
||||
CARGO_PROFILE_RELEASE_OPT_LEVEL: "1"
|
||||
CARGO_PROFILE_RELEASE_CODEGEN_UNITS: "16"
|
||||
CARGO_PROFILE_RELEASE_LTO: "false"
|
||||
CARGO_PROFILE_RELEASE_STRIP: "true"
|
||||
CARGO_PROFILE_RELEASE_DEBUG: "false"
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
name: Build CI Docker Image
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- ".github/Dockerfile"
|
||||
- ".github/workflows/docker-ci-image.yml"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
jobs:
|
||||
build-image:
|
||||
name: Build and push CI image
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Log in to GHCR
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .github
|
||||
file: .github/Dockerfile
|
||||
push: true
|
||||
tags: |
|
||||
ghcr.io/tinyhumansai/openhuman_ci:latest
|
||||
ghcr.io/tinyhumansai/openhuman_ci:rust-1.93.0
|
||||
@@ -2,6 +2,7 @@ name: Installer Smoke
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
|
||||
@@ -375,6 +375,7 @@ jobs:
|
||||
MATRIX_TARGET: ${{ matrix.settings.target }}
|
||||
CORE_MANIFEST: ${{ steps.core-paths.outputs.core_manifest }}
|
||||
CORE_BIN_NAME: ${{ steps.core-paths.outputs.core_bin_name }}
|
||||
OPENHUMAN_SENTRY_DSN: ${{ vars.OPENHUMAN_SENTRY_DSN }}
|
||||
|
||||
- name: Stage sidecar for Tauri bundler
|
||||
shell: bash
|
||||
|
||||
+11
-67
@@ -2,6 +2,7 @@ name: Test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
|
||||
permissions:
|
||||
@@ -58,39 +59,21 @@ jobs:
|
||||
rust-tests:
|
||||
name: Rust Tests + Quality
|
||||
runs-on: ubuntu-22.04
|
||||
container:
|
||||
image: ghcr.io/tinyhumansai/openhuman_ci:rust-1.93.0
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Install Rust (rust-toolchain.toml)
|
||||
uses: dtolnay/rust-toolchain@1.93.0
|
||||
- name: Cache Rust build artifacts
|
||||
uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
components: rustfmt, clippy
|
||||
targets: x86_64-unknown-linux-gnu
|
||||
|
||||
- name: Install Tauri build dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf
|
||||
|
||||
# Match build.yml: lockfile body only (skip workspace version header noise)
|
||||
- name: Cargo.lock fingerprint (deps only)
|
||||
id: cargo-lock-fingerprint
|
||||
shell: bash
|
||||
run: |
|
||||
echo "hash=$(tail -n +8 Cargo.lock | openssl dgst -sha256 | awk '{print $2}')" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Cache Cargo registry and git sources
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
key: ${{ runner.os }}-rust-test-cargo-${{ steps.cargo-lock-fingerprint.outputs.hash }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-rust-test-cargo-
|
||||
workspaces: |
|
||||
. -> target
|
||||
app/src-tauri -> target
|
||||
cache-on-failure: true
|
||||
|
||||
- name: Check formatting (cargo fmt)
|
||||
run: cargo fmt --all -- --check
|
||||
@@ -102,52 +85,13 @@ jobs:
|
||||
run: cargo test -p openhuman
|
||||
|
||||
- name: Build sidecar core binary
|
||||
run: cargo build --manifest-path Cargo.toml --release --target x86_64-unknown-linux-gnu --bin openhuman-core
|
||||
run: cargo build --profile ci --target x86_64-unknown-linux-gnu --bin openhuman-core
|
||||
|
||||
- name: Stage sidecar for Tauri shell tests
|
||||
run: |
|
||||
mkdir -p app/src-tauri/binaries
|
||||
cp target/x86_64-unknown-linux-gnu/release/openhuman-core app/src-tauri/binaries/openhuman-core-x86_64-unknown-linux-gnu
|
||||
cp target/x86_64-unknown-linux-gnu/ci/openhuman-core app/src-tauri/binaries/openhuman-core-x86_64-unknown-linux-gnu
|
||||
chmod +x app/src-tauri/binaries/openhuman-core-x86_64-unknown-linux-gnu
|
||||
|
||||
- name: Test Tauri shell (OpenHuman)
|
||||
run: cargo test --manifest-path app/src-tauri/Cargo.toml
|
||||
|
||||
# e2e-macos:
|
||||
# name: E2E (macOS / Appium)
|
||||
# runs-on: macos-latest
|
||||
# timeout-minutes: 90
|
||||
# steps:
|
||||
# - name: Checkout code
|
||||
# uses: actions/checkout@v4
|
||||
# with:
|
||||
# fetch-depth: 1
|
||||
# submodules: recursive
|
||||
|
||||
# - name: Setup Node.js 24.x
|
||||
# uses: actions/setup-node@v4
|
||||
# with:
|
||||
# node-version: 24.x
|
||||
# cache: "yarn"
|
||||
|
||||
# - name: Install Rust (rust-toolchain.toml)
|
||||
# uses: dtolnay/rust-toolchain@1.93.0
|
||||
|
||||
# - name: Install dependencies
|
||||
# run: yarn install --frozen-lockfile
|
||||
|
||||
# - name: Ensure .env exists for E2E build
|
||||
# run: |
|
||||
# touch .env
|
||||
# touch app/.env
|
||||
|
||||
# - name: Install Appium and mac2 driver
|
||||
# run: |
|
||||
# npm install -g appium
|
||||
# appium driver install mac2
|
||||
|
||||
# - name: Build E2E app bundle
|
||||
# run: yarn workspace openhuman-app test:e2e:build
|
||||
|
||||
# - name: Run all E2E flows
|
||||
# run: yarn workspace openhuman-app test:e2e:all:flows
|
||||
|
||||
@@ -2,6 +2,7 @@ name: Type Check
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
|
||||
permissions:
|
||||
@@ -61,37 +62,20 @@ jobs:
|
||||
rust-quality:
|
||||
name: Rust Quality (fmt + clippy)
|
||||
runs-on: ubuntu-22.04
|
||||
container:
|
||||
image: ghcr.io/tinyhumansai/openhuman_ci:rust-1.93.0
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Install Rust (rust-toolchain.toml)
|
||||
uses: dtolnay/rust-toolchain@1.93.0
|
||||
- name: Cache Rust build artifacts
|
||||
uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
components: rustfmt, clippy
|
||||
|
||||
- name: Install Tauri build dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf
|
||||
|
||||
- name: Cargo.lock fingerprint (deps only)
|
||||
id: cargo-lock-fingerprint
|
||||
shell: bash
|
||||
run: |
|
||||
echo "hash=$(tail -n +8 Cargo.lock | openssl dgst -sha256 | awk '{print $2}')" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Cache Cargo registry and git sources
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
key: ${{ runner.os }}-cargo-registry-${{ steps.cargo-lock-fingerprint.outputs.hash }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-cargo-registry-
|
||||
workspaces: |
|
||||
. -> target
|
||||
cache-on-failure: true
|
||||
|
||||
- name: Check formatting (cargo fmt)
|
||||
run: cargo fmt --all -- --check
|
||||
|
||||
+2
-1
@@ -57,4 +57,5 @@ tauri.key.pub
|
||||
/target/
|
||||
src-tauri/target/
|
||||
|
||||
workflow
|
||||
workflow
|
||||
.fastembed_cache
|
||||
|
||||
+10
@@ -120,3 +120,13 @@ 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"]
|
||||
|
||||
# Fast CI builds: trade runtime perf for compile speed
|
||||
[profile.ci]
|
||||
inherits = "release"
|
||||
opt-level = 1
|
||||
codegen-units = 16
|
||||
lto = false
|
||||
incremental = false
|
||||
strip = true
|
||||
debug = false
|
||||
|
||||
@@ -40,3 +40,13 @@ env_logger = "0.11"
|
||||
# This feature is used for production builds or when a dev server is not specified, DO NOT REMOVE!!
|
||||
custom-protocol = ["tauri/custom-protocol"]
|
||||
sandbox-bubblewrap = []
|
||||
|
||||
# Fast CI builds: trade runtime perf for compile speed
|
||||
[profile.ci]
|
||||
inherits = "release"
|
||||
opt-level = 1
|
||||
codegen-units = 16
|
||||
lto = false
|
||||
incremental = false
|
||||
strip = true
|
||||
debug = false
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
"UPDATER_ENDPOINT": "",
|
||||
"VITE_BACKEND_URL": "https://localhost:5005",
|
||||
"VITE_SKILLS_GITHUB_REPO": "",
|
||||
"OPENHUMAN_SENTRY_DSN": "",
|
||||
"VITE_SENTRY_DSN": "",
|
||||
"VITE_DEBUG": "true"
|
||||
}
|
||||
|
||||
@@ -3,7 +3,9 @@ use serde::{Deserialize, Serialize};
|
||||
/// Socket connection status
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
#[derive(Default)]
|
||||
pub enum ConnectionStatus {
|
||||
#[default]
|
||||
Disconnected,
|
||||
Connecting,
|
||||
Connected,
|
||||
@@ -11,12 +13,6 @@ pub enum ConnectionStatus {
|
||||
Error,
|
||||
}
|
||||
|
||||
impl Default for ConnectionStatus {
|
||||
fn default() -> Self {
|
||||
Self::Disconnected
|
||||
}
|
||||
}
|
||||
|
||||
/// Socket connection state emitted to frontend
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct SocketState {
|
||||
|
||||
@@ -247,7 +247,7 @@ fn emit_with_aliases(socket: &SocketRef, name: &str, payload: &serde_json::Value
|
||||
}
|
||||
|
||||
fn emit_room_with_aliases(io: &SocketIo, room: &str, name: &str, payload: &serde_json::Value) {
|
||||
let _ = io.to(room.to_string()).emit(name.to_string(), payload);
|
||||
let _ = io.to(room.to_string()).emit(name, payload);
|
||||
if let Some(alias) = event_alias(name) {
|
||||
let _ = io.to(room.to_string()).emit(alias, payload);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@ fn main() {
|
||||
dsn: std::env::var("OPENHUMAN_SENTRY_DSN")
|
||||
.ok()
|
||||
.filter(|s| !s.is_empty())
|
||||
.or_else(|| option_env!("OPENHUMAN_SENTRY_DSN").map(|s| s.to_string()))
|
||||
.filter(|s| !s.is_empty())
|
||||
.and_then(|s| s.parse().ok()),
|
||||
release: Some(std::borrow::Cow::Borrowed(env!("CARGO_PKG_VERSION"))),
|
||||
environment: Some(if cfg!(debug_assertions) {
|
||||
|
||||
@@ -54,6 +54,12 @@ pub struct AgentBuilder {
|
||||
available_hints: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
impl Default for AgentBuilder {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl AgentBuilder {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
|
||||
@@ -7,6 +7,12 @@ pub use crate::openhuman::agent::traits::RuntimeAdapter;
|
||||
|
||||
pub struct NativeRuntime;
|
||||
|
||||
impl Default for NativeRuntime {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl NativeRuntime {
|
||||
pub const fn new() -> Self {
|
||||
Self
|
||||
|
||||
@@ -27,7 +27,7 @@ pub(crate) fn autosave_memory_key(prefix: &str) -> String {
|
||||
/// Preserves the system prompt (first message if role=system) and the most recent messages.
|
||||
pub(crate) fn trim_history(history: &mut Vec<ChatMessage>, max_history: usize) {
|
||||
// Nothing to trim if within limit
|
||||
let has_system = history.first().map_or(false, |m| m.role == "system");
|
||||
let has_system = history.first().is_some_and(|m| m.role == "system");
|
||||
let non_system_count = if has_system {
|
||||
history.len() - 1
|
||||
} else {
|
||||
@@ -74,7 +74,7 @@ pub(crate) async fn auto_compact_history(
|
||||
max_history: usize,
|
||||
config: &Config,
|
||||
) -> Result<bool> {
|
||||
let has_system = history.first().map_or(false, |m| m.role == "system");
|
||||
let has_system = history.first().is_some_and(|m| m.role == "system");
|
||||
let non_system_count = if has_system {
|
||||
history.len().saturating_sub(1)
|
||||
} else {
|
||||
|
||||
@@ -226,7 +226,7 @@ pub(crate) async fn run_tool_call_loop(
|
||||
if r.success {
|
||||
scrub_credentials(&r.output)
|
||||
} else {
|
||||
format!("Error: {}", r.error.unwrap_or_else(|| r.output))
|
||||
format!("Error: {}", r.error.unwrap_or(r.output))
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
|
||||
@@ -364,10 +364,7 @@ fn validate_size(source: &str, size_bytes: usize, max_bytes: usize) -> anyhow::R
|
||||
}
|
||||
|
||||
fn validate_mime(source: &str, mime: &str) -> anyhow::Result<()> {
|
||||
if ALLOWED_IMAGE_MIME_TYPES
|
||||
.iter()
|
||||
.any(|allowed| *allowed == mime)
|
||||
{
|
||||
if ALLOWED_IMAGE_MIME_TYPES.contains(&mime) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
|
||||
@@ -241,6 +241,12 @@ pub struct AutocompleteEngine {
|
||||
inner: Mutex<EngineState>,
|
||||
}
|
||||
|
||||
impl Default for AutocompleteEngine {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl AutocompleteEngine {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
@@ -908,8 +914,7 @@ fn show_overflow_badge(
|
||||
fn escape_osascript_text(raw: &str) -> String {
|
||||
raw.replace('\\', "\\\\")
|
||||
.replace('\"', "\\\"")
|
||||
.replace('\n', " ")
|
||||
.replace('\r', " ")
|
||||
.replace(['\n', '\r'], " ")
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
|
||||
@@ -6,6 +6,12 @@ use uuid::Uuid;
|
||||
/// Console channel — stdin/stdout, not used in the web UI, zero deps
|
||||
pub struct CliChannel;
|
||||
|
||||
impl Default for CliChannel {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl CliChannel {
|
||||
pub fn new() -> Self {
|
||||
Self
|
||||
|
||||
@@ -130,7 +130,7 @@ impl ChannelDefinition {
|
||||
.filter(|f| {
|
||||
credentials
|
||||
.get(f.key)
|
||||
.map_or(true, |v| v.as_str().map_or(false, |s| s.is_empty()))
|
||||
.is_none_or(|v| v.as_str().is_some_and(|s| s.is_empty()))
|
||||
})
|
||||
.map(|f| f.key)
|
||||
.collect();
|
||||
|
||||
@@ -195,7 +195,7 @@ impl Channel for DingTalkChannel {
|
||||
"data": "",
|
||||
});
|
||||
|
||||
if let Err(e) = write.send(Message::Text(pong.to_string().into())).await {
|
||||
if let Err(e) = write.send(Message::Text(pong.to_string())).await {
|
||||
tracing::warn!("DingTalk: failed to send pong: {e}");
|
||||
break;
|
||||
}
|
||||
@@ -262,7 +262,7 @@ impl Channel for DingTalkChannel {
|
||||
"message": "OK",
|
||||
"data": "",
|
||||
});
|
||||
let _ = write.send(Message::Text(ack.to_string().into())).await;
|
||||
let _ = write.send(Message::Text(ack.to_string())).await;
|
||||
|
||||
let channel_msg = ChannelMessage {
|
||||
id: Uuid::new_v4().to_string(),
|
||||
|
||||
@@ -272,9 +272,7 @@ impl Channel for DiscordChannel {
|
||||
}
|
||||
}
|
||||
});
|
||||
write
|
||||
.send(Message::Text(identify.to_string().into()))
|
||||
.await?;
|
||||
write.send(Message::Text(identify.to_string())).await?;
|
||||
|
||||
tracing::info!("Discord: connected and identified");
|
||||
|
||||
@@ -303,7 +301,7 @@ impl Channel for DiscordChannel {
|
||||
_ = hb_rx.recv() => {
|
||||
let d = if sequence >= 0 { json!(sequence) } else { json!(null) };
|
||||
let hb = json!({"op": 1, "d": d});
|
||||
if write.send(Message::Text(hb.to_string().into())).await.is_err() {
|
||||
if write.send(Message::Text(hb.to_string())).await.is_err() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -331,7 +329,7 @@ impl Channel for DiscordChannel {
|
||||
1 => {
|
||||
let d = if sequence >= 0 { json!(sequence) } else { json!(null) };
|
||||
let hb = json!({"op": 1, "d": d});
|
||||
if write.send(Message::Text(hb.to_string().into())).await.is_err() {
|
||||
if write.send(Message::Text(hb.to_string())).await.is_err() {
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
|
||||
@@ -288,7 +288,7 @@ impl LarkChannel {
|
||||
payload: None,
|
||||
};
|
||||
if write
|
||||
.send(WsMsg::Binary(initial_ping.encode_to_vec().into()))
|
||||
.send(WsMsg::Binary(initial_ping.encode_to_vec()))
|
||||
.await
|
||||
.is_err()
|
||||
{
|
||||
@@ -309,7 +309,7 @@ impl LarkChannel {
|
||||
headers: vec![PbHeader { key: "type".into(), value: "ping".into() }],
|
||||
payload: None,
|
||||
};
|
||||
if write.send(WsMsg::Binary(ping.encode_to_vec().into())).await.is_err() {
|
||||
if write.send(WsMsg::Binary(ping.encode_to_vec())).await.is_err() {
|
||||
tracing::warn!("Lark: ping failed, reconnecting");
|
||||
break;
|
||||
}
|
||||
@@ -378,7 +378,7 @@ impl LarkChannel {
|
||||
let mut ack = frame.clone();
|
||||
ack.payload = Some(br#"{"code":200,"headers":{},"data":[]}"#.to_vec());
|
||||
ack.headers.push(PbHeader { key: "biz_rt".into(), value: "0".into() });
|
||||
let _ = write.send(WsMsg::Binary(ack.encode_to_vec().into())).await;
|
||||
let _ = write.send(WsMsg::Binary(ack.encode_to_vec())).await;
|
||||
}
|
||||
|
||||
// Fragment reassembly
|
||||
@@ -739,7 +739,7 @@ impl LarkChannel {
|
||||
let token_ok = payload
|
||||
.get("token")
|
||||
.and_then(|t| t.as_str())
|
||||
.map_or(true, |t| t == state.verification_token);
|
||||
.is_none_or(|t| t == state.verification_token);
|
||||
|
||||
if !token_ok {
|
||||
return (StatusCode::FORBIDDEN, "invalid token").into_response();
|
||||
|
||||
@@ -263,9 +263,7 @@ impl Channel for QQChannel {
|
||||
}
|
||||
}
|
||||
});
|
||||
write
|
||||
.send(Message::Text(identify.to_string().into()))
|
||||
.await?;
|
||||
write.send(Message::Text(identify.to_string())).await?;
|
||||
|
||||
tracing::info!("QQ: connected and identified");
|
||||
|
||||
@@ -290,7 +288,7 @@ impl Channel for QQChannel {
|
||||
let d = if sequence >= 0 { json!(sequence) } else { json!(null) };
|
||||
let hb = json!({"op": 1, "d": d});
|
||||
if write
|
||||
.send(Message::Text(hb.to_string().into()))
|
||||
.send(Message::Text(hb.to_string()))
|
||||
.await
|
||||
.is_err()
|
||||
{
|
||||
@@ -322,7 +320,7 @@ impl Channel for QQChannel {
|
||||
let d = if sequence >= 0 { json!(sequence) } else { json!(null) };
|
||||
let hb = json!({"op": 1, "d": d});
|
||||
if write
|
||||
.send(Message::Text(hb.to_string().into()))
|
||||
.send(Message::Text(hb.to_string()))
|
||||
.await
|
||||
.is_err()
|
||||
{
|
||||
|
||||
@@ -145,7 +145,7 @@ pub(crate) async fn process_channel_message(
|
||||
// Determine if this channel supports streaming draft updates
|
||||
let use_streaming = target_channel
|
||||
.as_ref()
|
||||
.map_or(false, |ch| ch.supports_draft_updates());
|
||||
.is_some_and(|ch| ch.supports_draft_updates());
|
||||
|
||||
// Set up streaming channel if supported
|
||||
let (delta_tx, delta_rx) = if use_streaming {
|
||||
|
||||
@@ -592,7 +592,10 @@ impl Config {
|
||||
}
|
||||
}
|
||||
|
||||
if let Ok(dsn) = std::env::var("OPENHUMAN_SENTRY_DSN") {
|
||||
let dsn_value = std::env::var("OPENHUMAN_SENTRY_DSN")
|
||||
.ok()
|
||||
.or_else(|| option_env!("OPENHUMAN_SENTRY_DSN").map(|s| s.to_string()));
|
||||
if let Some(dsn) = dsn_value {
|
||||
let dsn = dsn.trim();
|
||||
if !dsn.is_empty() {
|
||||
self.observability.sentry_dsn = Some(dsn.to_string());
|
||||
|
||||
@@ -150,7 +150,6 @@ fn ensure_fastembed_ort_dylib_path() {
|
||||
|
||||
if bundled.exists() {
|
||||
env::set_var("ORT_DYLIB_PATH", bundled);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,17 +14,13 @@ const DEFAULT_CHUNK_TOKENS: usize = 225;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[derive(Default)]
|
||||
pub enum ExtractionMode {
|
||||
#[default]
|
||||
Sentence,
|
||||
Chunk,
|
||||
}
|
||||
|
||||
impl Default for ExtractionMode {
|
||||
fn default() -> Self {
|
||||
Self::Sentence
|
||||
}
|
||||
}
|
||||
|
||||
impl ExtractionMode {
|
||||
fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
@@ -282,7 +278,7 @@ fn type_allowed(actual: &str, allowed: &[&str]) -> bool {
|
||||
|
||||
fn resolve_person_alias(name: &str, known_people: &HashMap<String, String>) -> String {
|
||||
let upper = name.to_uppercase();
|
||||
known_people.get(&upper).cloned().unwrap_or_else(|| upper)
|
||||
known_people.get(&upper).cloned().unwrap_or(upper)
|
||||
}
|
||||
|
||||
impl ExtractionAccumulator {
|
||||
@@ -1297,7 +1293,7 @@ async fn parse_document(
|
||||
|
||||
for unit in build_units(&chunks, config.extraction_mode) {
|
||||
if let Some(ref runtime) = relex_runtime {
|
||||
apply_model_extraction(&runtime, &unit, &mut accumulator, config);
|
||||
apply_model_extraction(runtime, &unit, &mut accumulator, config);
|
||||
}
|
||||
|
||||
if let Some(captures) = recipient_regex().captures(&unit.text) {
|
||||
|
||||
@@ -5,6 +5,7 @@ use std::sync::OnceLock;
|
||||
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use futures_util::TryStreamExt;
|
||||
#[cfg(target_os = "windows")]
|
||||
use glob::glob;
|
||||
use ndarray::{Array, Array2, Array3, Array4, Ix2, Ix3, Ix4};
|
||||
use ort::{
|
||||
@@ -433,10 +434,11 @@ async fn resolve_bundle_dir(model_name: &str) -> Option<PathBuf> {
|
||||
return Some(bundle_dir);
|
||||
}
|
||||
|
||||
if uses_default_bundle(model_name) {
|
||||
if ensure_managed_bundle(&managed_dir).await.is_ok() && bundle_complete(&managed_dir) {
|
||||
return Some(managed_dir);
|
||||
}
|
||||
if uses_default_bundle(model_name)
|
||||
&& ensure_managed_bundle(&managed_dir).await.is_ok()
|
||||
&& bundle_complete(&managed_dir)
|
||||
{
|
||||
return Some(managed_dir);
|
||||
}
|
||||
|
||||
None
|
||||
@@ -482,6 +484,7 @@ fn model_file_path(bundle_dir: &Path) -> Option<PathBuf> {
|
||||
None
|
||||
}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
fn ensure_ort_dylib_path(bundle_dir: &Path) {
|
||||
if env::var_os("ORT_DYLIB_PATH").is_some() {
|
||||
return;
|
||||
@@ -505,7 +508,6 @@ fn ensure_ort_dylib_path(bundle_dir: &Path) {
|
||||
let runtime_lib = candidate.join(ORT_DYLIB_FILE_NAME);
|
||||
if runtime_lib.exists() {
|
||||
env::set_var("ORT_DYLIB_PATH", runtime_lib);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -788,8 +790,8 @@ fn decode_entity_spans(
|
||||
) -> Vec<DecodedSpan> {
|
||||
let mut spans = Vec::new();
|
||||
let num_words = tokens.len();
|
||||
let width_count = logits.shape().get(2).copied().unwrap_or_default() as usize;
|
||||
let class_count = logits.shape().get(3).copied().unwrap_or_default() as usize;
|
||||
let width_count = logits.shape().get(2).copied().unwrap_or_default();
|
||||
let class_count = logits.shape().get(3).copied().unwrap_or_default();
|
||||
|
||||
for start in 0..num_words {
|
||||
let actual_max_width = max_width
|
||||
|
||||
@@ -385,12 +385,10 @@ fn pick_column_expr<'a>(
|
||||
}
|
||||
|
||||
fn pick_optional_column_expr<'a>(columns: &'a [String], candidates: &[&'a str]) -> Option<&'a str> {
|
||||
for candidate in candidates {
|
||||
if columns.iter().any(|c| c.eq_ignore_ascii_case(candidate)) {
|
||||
return Some(candidate);
|
||||
}
|
||||
}
|
||||
None
|
||||
candidates
|
||||
.iter()
|
||||
.find(|&candidate| columns.iter().any(|c| c.eq_ignore_ascii_case(candidate)))
|
||||
.map(|v| v as _)
|
||||
}
|
||||
|
||||
async fn next_available_key(memory: &dyn Memory, key: &str) -> Result<String> {
|
||||
|
||||
@@ -1138,10 +1138,7 @@ impl Provider for OpenAiCompatibleProvider {
|
||||
// If tool_calls are present, serialize the full message as JSON
|
||||
// so parse_tool_calls can handle the OpenAI-style format
|
||||
if c.message.tool_calls.is_some()
|
||||
&& c.message
|
||||
.tool_calls
|
||||
.as_ref()
|
||||
.map_or(false, |t| !t.is_empty())
|
||||
&& c.message.tool_calls.as_ref().is_some_and(|t| !t.is_empty())
|
||||
{
|
||||
serde_json::to_string(&c.message)
|
||||
.unwrap_or_else(|_| c.message.effective_content())
|
||||
@@ -1245,10 +1242,7 @@ impl Provider for OpenAiCompatibleProvider {
|
||||
// If tool_calls are present, serialize the full message as JSON
|
||||
// so parse_tool_calls can handle the OpenAI-style format
|
||||
if c.message.tool_calls.is_some()
|
||||
&& c.message
|
||||
.tool_calls
|
||||
.as_ref()
|
||||
.map_or(false, |t| !t.is_empty())
|
||||
&& c.message.tool_calls.as_ref().is_some_and(|t| !t.is_empty())
|
||||
{
|
||||
serde_json::to_string(&c.message)
|
||||
.unwrap_or_else(|_| c.message.effective_content())
|
||||
|
||||
@@ -170,7 +170,7 @@ pub fn create_backend_inference_provider(
|
||||
) -> anyhow::Result<Box<dyn Provider>> {
|
||||
let resolved = resolve_provider_credential(INFERENCE_BACKEND_ID, api_key)
|
||||
.map(|v| String::from_utf8(v.into_bytes()).unwrap_or_default());
|
||||
let key = resolved.as_ref().map(String::as_str);
|
||||
let key = resolved.as_deref();
|
||||
Ok(Box::new(openhuman_backend::OpenHumanBackendProvider::new(
|
||||
api_url, key, options,
|
||||
)))
|
||||
|
||||
@@ -146,7 +146,7 @@ pub(crate) fn capture_screen_image_ref_for_context(
|
||||
}
|
||||
|
||||
let encoded = BASE64_STANDARD.encode(bytes);
|
||||
return Ok(format!("data:image/png;base64,{encoded}"));
|
||||
Ok(format!("data:image/png;base64,{encoded}"))
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
|
||||
@@ -39,6 +39,12 @@ pub struct ActionTracker {
|
||||
actions: Mutex<Vec<Instant>>,
|
||||
}
|
||||
|
||||
impl Default for ActionTracker {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl ActionTracker {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
|
||||
@@ -33,7 +33,7 @@ pub fn install(config: &Config) -> Result<ServiceStatus> {
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
macos::install(config)?;
|
||||
return status(config);
|
||||
status(config)
|
||||
}
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
@@ -72,7 +72,7 @@ pub fn stop(config: &Config) -> Result<ServiceStatus> {
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
macos::stop(config)?;
|
||||
return status(config);
|
||||
status(config)
|
||||
}
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
|
||||
@@ -22,7 +22,7 @@ pub fn register<'js>(
|
||||
Function::new(
|
||||
ctx.clone(),
|
||||
move |name: String, version: u32| -> rquickjs::Result<String> {
|
||||
let result = s.open_database(&name, version).map_err(|e| js_err(e))?;
|
||||
let result = s.open_database(&name, version).map_err(js_err)?;
|
||||
serde_json::to_string(&result).map_err(|e| js_err(e.to_string()))
|
||||
},
|
||||
),
|
||||
@@ -44,7 +44,7 @@ pub fn register<'js>(
|
||||
ops.set(
|
||||
"idb_delete_database",
|
||||
Function::new(ctx.clone(), move |name: String| -> rquickjs::Result<()> {
|
||||
s.delete_database(&name).map_err(|e| js_err(e))
|
||||
s.delete_database(&name).map_err(js_err)
|
||||
}),
|
||||
)?;
|
||||
}
|
||||
@@ -64,7 +64,7 @@ pub fn register<'js>(
|
||||
let key_path = opts["keyPath"].as_str();
|
||||
let auto_increment = opts["autoIncrement"].as_bool().unwrap_or(false);
|
||||
s.create_object_store(&db_name, &store_name, key_path, auto_increment)
|
||||
.map_err(|e| js_err(e))
|
||||
.map_err(js_err)
|
||||
},
|
||||
),
|
||||
)?;
|
||||
@@ -77,8 +77,7 @@ pub fn register<'js>(
|
||||
Function::new(
|
||||
ctx.clone(),
|
||||
move |db_name: String, store_name: String| -> rquickjs::Result<()> {
|
||||
s.delete_object_store(&db_name, &store_name)
|
||||
.map_err(|e| js_err(e))
|
||||
s.delete_object_store(&db_name, &store_name).map_err(js_err)
|
||||
},
|
||||
),
|
||||
)?;
|
||||
@@ -96,9 +95,7 @@ pub fn register<'js>(
|
||||
-> rquickjs::Result<String> {
|
||||
let key_val: serde_json::Value =
|
||||
serde_json::from_str(&key).map_err(|e| js_err(e.to_string()))?;
|
||||
let result = s
|
||||
.get(&db_name, &store_name, &key_val)
|
||||
.map_err(|e| js_err(e))?;
|
||||
let result = s.get(&db_name, &store_name, &key_val).map_err(js_err)?;
|
||||
serde_json::to_string(&result).map_err(|e| js_err(e.to_string()))
|
||||
},
|
||||
),
|
||||
@@ -121,7 +118,7 @@ pub fn register<'js>(
|
||||
let value_val: serde_json::Value =
|
||||
serde_json::from_str(&value).map_err(|e| js_err(e.to_string()))?;
|
||||
s.put(&db_name, &store_name, &key_val, &value_val)
|
||||
.map_err(|e| js_err(e))
|
||||
.map_err(js_err)
|
||||
},
|
||||
),
|
||||
)?;
|
||||
@@ -136,8 +133,7 @@ pub fn register<'js>(
|
||||
move |db_name: String, store_name: String, key: String| -> rquickjs::Result<()> {
|
||||
let key_val: serde_json::Value =
|
||||
serde_json::from_str(&key).map_err(|e| js_err(e.to_string()))?;
|
||||
s.delete(&db_name, &store_name, &key_val)
|
||||
.map_err(|e| js_err(e))
|
||||
s.delete(&db_name, &store_name, &key_val).map_err(js_err)
|
||||
},
|
||||
),
|
||||
)?;
|
||||
@@ -150,7 +146,7 @@ pub fn register<'js>(
|
||||
Function::new(
|
||||
ctx.clone(),
|
||||
move |db_name: String, store_name: String| -> rquickjs::Result<()> {
|
||||
s.clear(&db_name, &store_name).map_err(|e| js_err(e))
|
||||
s.clear(&db_name, &store_name).map_err(js_err)
|
||||
},
|
||||
),
|
||||
)?;
|
||||
@@ -166,9 +162,7 @@ pub fn register<'js>(
|
||||
store_name: String,
|
||||
count: Option<u32>|
|
||||
-> rquickjs::Result<String> {
|
||||
let result = s
|
||||
.get_all(&db_name, &store_name, count)
|
||||
.map_err(|e| js_err(e))?;
|
||||
let result = s.get_all(&db_name, &store_name, count).map_err(js_err)?;
|
||||
serde_json::to_string(&result).map_err(|e| js_err(e.to_string()))
|
||||
},
|
||||
),
|
||||
@@ -187,7 +181,7 @@ pub fn register<'js>(
|
||||
-> rquickjs::Result<String> {
|
||||
let result = s
|
||||
.get_all_keys(&db_name, &store_name, count)
|
||||
.map_err(|e| js_err(e))?;
|
||||
.map_err(js_err)?;
|
||||
serde_json::to_string(&result).map_err(|e| js_err(e.to_string()))
|
||||
},
|
||||
),
|
||||
@@ -201,7 +195,7 @@ pub fn register<'js>(
|
||||
Function::new(
|
||||
ctx.clone(),
|
||||
move |db_name: String, store_name: String| -> rquickjs::Result<u32> {
|
||||
s.count(&db_name, &store_name).map_err(|e| js_err(e))
|
||||
s.count(&db_name, &store_name).map_err(js_err)
|
||||
},
|
||||
),
|
||||
)?;
|
||||
@@ -226,7 +220,7 @@ pub fn register<'js>(
|
||||
};
|
||||
let rows = s
|
||||
.skill_db_exec(&sc.skill_id, &sql, ¶ms)
|
||||
.map_err(|e| js_err(e))?;
|
||||
.map_err(js_err)?;
|
||||
Ok(rows as i64)
|
||||
},
|
||||
),
|
||||
@@ -248,7 +242,7 @@ pub fn register<'js>(
|
||||
};
|
||||
let result = s
|
||||
.skill_db_get(&sc.skill_id, &sql, ¶ms)
|
||||
.map_err(|e| js_err(e))?;
|
||||
.map_err(js_err)?;
|
||||
serde_json::to_string(&result).map_err(|e| js_err(e.to_string()))
|
||||
},
|
||||
),
|
||||
@@ -270,7 +264,7 @@ pub fn register<'js>(
|
||||
};
|
||||
let result = s
|
||||
.skill_db_all(&sc.skill_id, &sql, ¶ms)
|
||||
.map_err(|e| js_err(e))?;
|
||||
.map_err(js_err)?;
|
||||
serde_json::to_string(&result).map_err(|e| js_err(e.to_string()))
|
||||
},
|
||||
),
|
||||
@@ -285,7 +279,7 @@ pub fn register<'js>(
|
||||
Function::new(
|
||||
ctx.clone(),
|
||||
move |key: String| -> rquickjs::Result<String> {
|
||||
let result = s.skill_kv_get(&sc.skill_id, &key).map_err(|e| js_err(e))?;
|
||||
let result = s.skill_kv_get(&sc.skill_id, &key).map_err(js_err)?;
|
||||
serde_json::to_string(&result).map_err(|e| js_err(e.to_string()))
|
||||
},
|
||||
),
|
||||
@@ -302,8 +296,7 @@ pub fn register<'js>(
|
||||
move |key: String, value_json: String| -> rquickjs::Result<()> {
|
||||
let value: serde_json::Value =
|
||||
serde_json::from_str(&value_json).map_err(|e| js_err(e.to_string()))?;
|
||||
s.skill_kv_set(&sc.skill_id, &key, &value)
|
||||
.map_err(|e| js_err(e))
|
||||
s.skill_kv_set(&sc.skill_id, &key, &value).map_err(js_err)
|
||||
},
|
||||
),
|
||||
)?;
|
||||
@@ -321,9 +314,7 @@ pub fn register<'js>(
|
||||
Function::new(
|
||||
ctx.clone(),
|
||||
move |key: String| -> rquickjs::Result<String> {
|
||||
let result = s
|
||||
.skill_store_get(&sc.skill_id, &key)
|
||||
.map_err(|e| js_err(e))?;
|
||||
let result = s.skill_store_get(&sc.skill_id, &key).map_err(js_err)?;
|
||||
serde_json::to_string(&result).map_err(|e| js_err(e.to_string()))
|
||||
},
|
||||
),
|
||||
@@ -341,7 +332,7 @@ pub fn register<'js>(
|
||||
let value: serde_json::Value =
|
||||
serde_json::from_str(&value_json).map_err(|e| js_err(e.to_string()))?;
|
||||
s.skill_store_set(&sc.skill_id, &key, &value)
|
||||
.map_err(|e| js_err(e))
|
||||
.map_err(js_err)
|
||||
},
|
||||
),
|
||||
)?;
|
||||
@@ -353,8 +344,7 @@ pub fn register<'js>(
|
||||
ops.set(
|
||||
"store_delete",
|
||||
Function::new(ctx.clone(), move |key: String| -> rquickjs::Result<()> {
|
||||
s.skill_store_delete(&sc.skill_id, &key)
|
||||
.map_err(|e| js_err(e))
|
||||
s.skill_store_delete(&sc.skill_id, &key).map_err(js_err)
|
||||
}),
|
||||
)?;
|
||||
}
|
||||
@@ -365,7 +355,7 @@ pub fn register<'js>(
|
||||
ops.set(
|
||||
"store_keys",
|
||||
Function::new(ctx.clone(), move || -> rquickjs::Result<String> {
|
||||
let keys = s.skill_store_keys(&sc.skill_id).map_err(|e| js_err(e))?;
|
||||
let keys = s.skill_store_keys(&sc.skill_id).map_err(js_err)?;
|
||||
serde_json::to_string(&keys).map_err(|e| js_err(e.to_string()))
|
||||
}),
|
||||
)?;
|
||||
|
||||
@@ -131,8 +131,8 @@ pub async fn registry_search(
|
||||
|
||||
let mut results: Vec<RegistrySkillEntry> = Vec::new();
|
||||
|
||||
let include_core = category.map_or(true, |c| c == "core");
|
||||
let include_third_party = category.map_or(true, |c| c == "third_party");
|
||||
let include_core = category.is_none_or(|c| c == "core");
|
||||
let include_third_party = category.is_none_or(|c| c == "third_party");
|
||||
|
||||
if include_core {
|
||||
results.extend(
|
||||
@@ -343,7 +343,7 @@ pub async fn skills_list_available(
|
||||
.map(|s| s.version.clone());
|
||||
let update_available = installed_version
|
||||
.as_ref()
|
||||
.map_or(false, |v| !v.is_empty() && *v != entry.version);
|
||||
.is_some_and(|v| !v.is_empty() && *v != entry.version);
|
||||
|
||||
available.push(AvailableSkillEntry {
|
||||
registry: entry,
|
||||
|
||||
@@ -327,7 +327,7 @@ async fn run_connection(
|
||||
// 5. Send Socket.IO CONNECT with auth
|
||||
let connect_payload = json!({"token": token});
|
||||
let connect_msg = format!("40{}", serde_json::to_string(&connect_payload).unwrap());
|
||||
if let Err(e) = ws_write.send(WsMessage::Text(connect_msg.into())).await {
|
||||
if let Err(e) = ws_write.send(WsMessage::Text(connect_msg)).await {
|
||||
return ConnectionOutcome::Failed(format!("Send SIO CONNECT: {e}"));
|
||||
}
|
||||
|
||||
@@ -386,7 +386,7 @@ async fn run_connection(
|
||||
outgoing = emit_rx.recv() => {
|
||||
match outgoing {
|
||||
Some(msg) => {
|
||||
if let Err(e) = ws_write.send(WsMessage::Text(msg.into())).await {
|
||||
if let Err(e) = ws_write.send(WsMessage::Text(msg)).await {
|
||||
return ConnectionOutcome::Lost(format!("Send failed: {e}"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,8 +6,10 @@ use std::collections::HashMap;
|
||||
/// Status of a running skill instance.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
#[derive(Default)]
|
||||
pub enum SkillStatus {
|
||||
/// Skill is registered but not yet started.
|
||||
#[default]
|
||||
Pending,
|
||||
/// Skill is currently initializing (loading JS, running init()).
|
||||
Initializing,
|
||||
@@ -21,12 +23,6 @@ pub enum SkillStatus {
|
||||
Error,
|
||||
}
|
||||
|
||||
impl Default for SkillStatus {
|
||||
fn default() -> Self {
|
||||
Self::Pending
|
||||
}
|
||||
}
|
||||
|
||||
/// Messages sent to a skill instance's message loop.
|
||||
#[derive(Debug)]
|
||||
pub enum SkillMessage {
|
||||
|
||||
@@ -62,7 +62,7 @@ where
|
||||
.enable_all()
|
||||
.build()
|
||||
.map_err(|e| format!("Failed to create runtime: {}", e))
|
||||
.and_then(|rt| Ok(rt.block_on(future)));
|
||||
.map(|rt| rt.block_on(future));
|
||||
|
||||
let result = match runtime_result {
|
||||
Ok(value) => Ok(value),
|
||||
|
||||
@@ -21,7 +21,9 @@ pub async fn try_dispatch(
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use futures_util::FutureExt;
|
||||
use serde_json::json;
|
||||
use std::panic::AssertUnwindSafe;
|
||||
|
||||
use super::try_dispatch;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user