diff --git a/.claude/memory.md b/.claude/memory.md
index b7631efb1c..3731748cb8 100644
--- a/.claude/memory.md
+++ b/.claude/memory.md
@@ -210,6 +210,13 @@ Quick reference for anyone starting with Claude on this project. Updated by the
- **Kill stuck processes** — `lsof -i :7788` then `kill `. Splitting the two prevents the
+ # smoke job from paying matrix overhead for a 2-spec run.
e2e-linux:
- if: inputs.run_linux
+ if: inputs.run_linux && !inputs.full
name: E2E (Linux / Appium Chromium)
runs-on: ubuntu-22.04
container:
@@ -73,6 +77,7 @@ jobs:
with:
ref: ${{ inputs.ref }}
fetch-depth: 1
+ persist-credentials: false
submodules: recursive
- name: Cache pnpm store
@@ -113,7 +118,7 @@ jobs:
key: appium3-chromium-${{ runner.os }}-v1
- name: Install JS dependencies
- run: pnpm install --frozen-lockfile
+ run: bash scripts/ci-cancel-aware.sh pnpm install --frozen-lockfile
- name: Ensure .env exists for E2E build
run: |
@@ -123,43 +128,238 @@ jobs:
- name: Install Appium and chromium driver
run: |
if ! command -v appium >/dev/null 2>&1; then
- npm install -g appium@3
+ bash scripts/ci-cancel-aware.sh npm install -g appium@3
fi
# `appium driver list --installed` can miss cached installs on some
# Appium builds; install idempotently and ignore "already installed".
- appium driver install --source=npm appium-chromium-driver >/dev/null 2>&1 || true
+ bash scripts/ci-cancel-aware.sh appium driver install --source=npm appium-chromium-driver >/dev/null 2>&1 || true
- name: Build E2E app
- run: pnpm --filter openhuman-app test:e2e:build
-
- - name: Run E2E (smoke)
- if: ${{ !inputs.full }}
- run: |
- xvfb-run -a --server-args="-screen 0 1280x960x24" \
- bash app/scripts/e2e-run-session.sh test/e2e/specs/smoke.spec.ts smoke
+ run: bash scripts/ci-cancel-aware.sh pnpm --filter openhuman-app test:e2e:build
- # Mega-flow exercises the OAuth-success-deep-link and Composio
- # trigger-lifecycle paths. Hard-fails on regressions — if the
- # deep-link → custom-event propagation race resurfaces, fix it
- # at the source rather than re-adding `continue-on-error`.
- name: Run E2E (mega-flow)
if: ${{ !inputs.full }}
run: |
- xvfb-run -a --server-args="-screen 0 1280x960x24" \
+ bash scripts/ci-cancel-aware.sh \
+ xvfb-run -a --server-args="-screen 0 1280x960x24" \
bash app/scripts/e2e-run-session.sh test/e2e/specs/mega-flow.spec.ts mega-flow
- - name: Run E2E (full suite)
- if: ${{ inputs.full }}
+ - name: Upload E2E failure artifacts
+ if: failure()
+ uses: actions/upload-artifact@v5
+ with:
+ name: e2e-failure-logs-${{ runner.os }}-${{ github.run_id }}
+ path: |
+ ${{ runner.temp }}/openhuman-e2e-app-*.log
+ /tmp/openhuman-e2e-app-*.log
+ app/test/e2e/artifacts/
+ retention-days: 7
+ if-no-files-found: ignore
+
+ - name: Write job summary
+ if: always()
+ run: |
+ echo "## E2E Results (${{ runner.os }})" >> $GITHUB_STEP_SUMMARY
+ echo "" >> $GITHUB_STEP_SUMMARY
+ if [ -f /tmp/e2e-summary.txt ]; then
+ cat /tmp/e2e-summary.txt >> $GITHUB_STEP_SUMMARY
+ else
+ echo "No summary file found." >> $GITHUB_STEP_SUMMARY
+ fi
+
+ # Full-suite Linux is now build-once-then-fanout: one `build-linux-full`
+ # job produces the binary + frontend dist + CEF runtime as a single
+ # workflow artifact, and the 4 shard test jobs `needs:` that build and
+ # download the artifact. This eliminates the parallel-shard cache race
+ # (only the first shard would otherwise populate the binary/CEF caches,
+ # the others would lose the race and rebuild) and guarantees the binary
+ # and its libcef.so are always packaged together.
+ build-linux-full:
+ if: inputs.run_linux && inputs.full
+ name: Build (Linux full)
+ runs-on: ubuntu-22.04
+ container:
+ image: ghcr.io/tinyhumansai/openhuman_ci:latest
+ timeout-minutes: 45
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v5
+ with:
+ ref: ${{ inputs.ref }}
+ fetch-depth: 1
+ submodules: recursive
+
+ - name: Cache pnpm store
+ uses: actions/cache@v5
+ with:
+ path: ~/.local/share/pnpm/store
+ key: pnpm-store-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}
+ restore-keys: |
+ pnpm-store-${{ runner.os }}-
+
+ - name: Cache Rust build artifacts
+ uses: Swatinem/rust-cache@v2
+ with:
+ workspaces: |
+ . -> target
+ app/src-tauri -> target
+ cache-on-failure: true
+ key: e2e-linux-unified
+
+ - name: Cache CEF binary distribution
+ uses: actions/cache@v5
+ with:
+ # cef-dll-sys downloads into $CEF_PATH; ensure-tauri-cli.sh +
+ # e2e-build.sh pin that to $HOME/Library/Caches/tauri-cef on
+ # every OS, so the cache key/path live there too.
+ path: |
+ ~/Library/Caches/tauri-cef
+ key: cef-x86_64-unknown-linux-gnu-v2-${{ hashFiles('app/src-tauri/Cargo.toml') }}
+ restore-keys: |
+ cef-x86_64-unknown-linux-gnu-v2-
+
+ - name: Install JS dependencies
+ run: bash scripts/ci-cancel-aware.sh pnpm install --frozen-lockfile
+
+ - name: Ensure .env exists for E2E build
run: |
- xvfb-run -a --server-args="-screen 0 1280x960x24" \
- bash app/scripts/e2e-run-session.sh
+ touch .env
+ touch app/.env
+
+ - name: Build E2E app
+ run: bash scripts/ci-cancel-aware.sh pnpm --filter openhuman-app test:e2e:build
- # Artifact uploads intentionally omitted — this reusable workflow
- # is invoked from release-staging.yml and release-production.yml,
- # and uploaded logs can carry mock-backend payloads, env-var
- # echoes, and CDP transcripts that we don't want pinned to a
- # release artifact. Local repro: rerun the spec via Docker and
- # the same logs land in /tmp.
+ - name: Package build artifact
+ run: |
+ # Stage everything the test shards need at the layout they expect
+ # under a single directory, so the consumer can extract straight
+ # into the workspace + $HOME.
+ STAGE="$(mktemp -d)"
+ mkdir -p "$STAGE/repo/app/src-tauri/target/debug"
+ mkdir -p "$STAGE/repo/app/dist"
+ mkdir -p "$STAGE/home/Library/Caches"
+ cp -a app/src-tauri/target/debug/OpenHuman "$STAGE/repo/app/src-tauri/target/debug/"
+ cp -a app/dist/. "$STAGE/repo/app/dist/"
+ cp -a "$HOME/Library/Caches/tauri-cef" "$STAGE/home/Library/Caches/tauri-cef"
+ tar -czf e2e-build-linux.tar.gz -C "$STAGE" repo home
+ ls -lh e2e-build-linux.tar.gz
+
+ - name: Upload build artifact
+ uses: actions/upload-artifact@v5
+ with:
+ name: e2e-build-linux-${{ github.run_id }}
+ path: e2e-build-linux.tar.gz
+ retention-days: 1
+ if-no-files-found: error
+
+ e2e-linux-full:
+ if: inputs.run_linux && inputs.full
+ needs: build-linux-full
+ name: E2E (Linux full / ${{ matrix.shard.name }})
+ runs-on: ubuntu-22.04
+ container:
+ image: ghcr.io/tinyhumansai/openhuman_ci:latest
+ timeout-minutes: 60
+ strategy:
+ fail-fast: false
+ matrix:
+ shard:
+ - { name: foundation, suites: "auth,navigation,system" }
+ - { name: chat, suites: "chat,skills,journeys" }
+ - { name: providers, suites: "providers,notifications" }
+ - { name: webhooks, suites: "webhooks" }
+ - { name: connectors, suites: "connectors" }
+ - { name: commerce, suites: "payments,settings" }
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v5
+ with:
+ ref: ${{ inputs.ref }}
+ fetch-depth: 1
+ submodules: recursive
+
+ - name: Cache pnpm store
+ uses: actions/cache@v5
+ with:
+ path: ~/.local/share/pnpm/store
+ key: pnpm-store-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}
+ restore-keys: |
+ pnpm-store-${{ runner.os }}-
+
+ - name: Cache Appium global install
+ uses: actions/cache@v5
+ with:
+ path: |
+ ~/.appium
+ /usr/local/lib/node_modules/appium
+ key: appium3-chromium-${{ runner.os }}-v1
+
+ - name: Install JS dependencies (for test harness only)
+ run: bash scripts/ci-cancel-aware.sh pnpm install --frozen-lockfile
+
+ - name: Install Appium and chromium driver
+ run: |
+ if ! command -v appium >/dev/null 2>&1; then
+ bash scripts/ci-cancel-aware.sh npm install -g appium@3
+ fi
+ bash scripts/ci-cancel-aware.sh appium driver install --source=npm appium-chromium-driver >/dev/null 2>&1 || true
+
+ - name: Download build artifact
+ uses: actions/download-artifact@v5
+ with:
+ name: e2e-build-linux-${{ github.run_id }}
+ path: .
+
+ - name: Restore build artifact into workspace + $HOME
+ run: |
+ tar -xzf e2e-build-linux.tar.gz
+ # The artifact contains: repo/{app/src-tauri/target/debug/OpenHuman, app/dist/...}
+ # and home/Library/Caches/tauri-cef/...
+ mkdir -p app/src-tauri/target/debug app/dist "$HOME/Library/Caches"
+ cp -a repo/app/src-tauri/target/debug/OpenHuman app/src-tauri/target/debug/
+ cp -a repo/app/dist/. app/dist/
+ cp -a home/Library/Caches/tauri-cef "$HOME/Library/Caches/"
+ rm -rf repo home e2e-build-linux.tar.gz
+ chmod +x app/src-tauri/target/debug/OpenHuman
+ ls -la app/src-tauri/target/debug/OpenHuman app/dist | head
+ ls -la "$HOME/Library/Caches/tauri-cef" | head
+
+ - name: Run E2E shard (${{ matrix.shard.name }} — suites=${{ matrix.shard.suites }})
+ env:
+ E2E_BAIL_ON_FAILURE: ${{ vars.E2E_BAIL_ON_FAILURE || '' }}
+ run: |
+ export CEF_PATH="$HOME/Library/Caches/tauri-cef"
+ BAIL_FLAG=""
+ if [[ "${E2E_BAIL_ON_FAILURE:-}" == "1" ]]; then
+ BAIL_FLAG="--bail"
+ fi
+ bash scripts/ci-cancel-aware.sh \
+ xvfb-run -a --server-args="-screen 0 1280x960x24" \
+ bash app/scripts/e2e-run-all-flows.sh --skip-preflight \
+ --suite=${{ matrix.shard.suites }} $BAIL_FLAG
+
+ - name: Upload E2E failure artifacts
+ if: failure()
+ uses: actions/upload-artifact@v5
+ with:
+ name: e2e-failure-logs-${{ runner.os }}-${{ matrix.shard.name }}-${{ github.run_id }}
+ path: |
+ ${{ runner.temp }}/openhuman-e2e-app-*.log
+ /tmp/openhuman-e2e-app-*.log
+ app/test/e2e/artifacts/
+ retention-days: 7
+ if-no-files-found: ignore
+
+ - name: Write job summary
+ if: always()
+ run: |
+ echo "## E2E Results (${{ runner.os }} / ${{ matrix.shard.name }})" >> $GITHUB_STEP_SUMMARY
+ echo "" >> $GITHUB_STEP_SUMMARY
+ if [ -f /tmp/e2e-summary.txt ]; then
+ cat /tmp/e2e-summary.txt >> $GITHUB_STEP_SUMMARY
+ else
+ echo "No summary file found." >> $GITHUB_STEP_SUMMARY
+ fi
# Rust-side E2E counterpart to the Tauri runs above. Same Linux-only
# scope (CI does not run this on macOS or Windows — the Rust core is
@@ -174,14 +374,15 @@ jobs:
image: ghcr.io/tinyhumansai/openhuman_ci:latest
timeout-minutes: 60
env:
- CARGO_INCREMENTAL: '0'
- RUST_BACKTRACE: '1'
+ CARGO_INCREMENTAL: "0"
+ RUST_BACKTRACE: "1"
steps:
- name: Checkout code
uses: actions/checkout@v5
with:
ref: ${{ inputs.ref }}
fetch-depth: 1
+ persist-credentials: false
submodules: recursive
- name: Cache pnpm store
@@ -200,7 +401,7 @@ jobs:
key: rust-e2e-linux
- name: Install JS dependencies
- run: pnpm install --frozen-lockfile
+ run: bash scripts/ci-cancel-aware.sh pnpm install --frozen-lockfile
- name: Ensure .env exists for tests
run: |
@@ -208,14 +409,14 @@ jobs:
touch app/.env
- name: Run Rust E2E suite (tests/*_e2e.rs vs mock backend)
- run: pnpm test:rust:e2e
+ run: bash scripts/ci-cancel-aware.sh pnpm test:rust:e2e
# No artifact uploads here either — same release-workflow reuse
# concern as the Tauri job above. Mock-backend log lives at
# /tmp/openhuman-rust-e2e-mock.log for local docker repro.
e2e-macos:
- if: inputs.run_macos
+ if: inputs.run_macos && !inputs.full
name: E2E (macOS / Appium Chromium)
runs-on: macos-latest
timeout-minutes: 90
@@ -225,6 +426,7 @@ jobs:
with:
ref: ${{ inputs.ref }}
fetch-depth: 1
+ persist-credentials: false
submodules: recursive
- name: Install pnpm
@@ -276,7 +478,7 @@ jobs:
key: appium3-chromium-${{ runner.os }}-v1
- name: Install JS dependencies
- run: pnpm install --frozen-lockfile
+ run: bash scripts/ci-cancel-aware.sh pnpm install --frozen-lockfile
- name: Ensure .env exists for E2E build
run: |
@@ -286,14 +488,14 @@ jobs:
- name: Install Appium and chromium driver
run: |
if ! command -v appium >/dev/null 2>&1; then
- npm install -g appium@3
+ bash scripts/ci-cancel-aware.sh npm install -g appium@3
fi
# `appium driver list --installed` can miss cached installs on some
# Appium builds; install idempotently and ignore "already installed".
- appium driver install --source=npm appium-chromium-driver >/dev/null 2>&1 || true
+ bash scripts/ci-cancel-aware.sh appium driver install --source=npm appium-chromium-driver >/dev/null 2>&1 || true
- name: Build E2E app
- run: pnpm --filter openhuman-app test:e2e:build
+ run: bash scripts/ci-cancel-aware.sh pnpm --filter openhuman-app test:e2e:build
# macOS rejects dynamic-framework loads from unsigned bundles — adhoc
# signing satisfies the loader without a real developer-ID cert.
@@ -304,21 +506,15 @@ jobs:
codesign --verify --deep --verbose=2 \
app/src-tauri/target/debug/bundle/macos/OpenHuman.app
- - name: Run E2E (smoke + mega-flow)
- if: ${{ !inputs.full }}
+ - name: Run E2E (mega-flow)
run: |
- bash app/scripts/e2e-run-session.sh test/e2e/specs/smoke.spec.ts smoke
- bash app/scripts/e2e-run-session.sh test/e2e/specs/mega-flow.spec.ts mega-flow
-
- - name: Run E2E (full suite)
- if: ${{ inputs.full }}
- run: bash app/scripts/e2e-run-session.sh
+ bash scripts/ci-cancel-aware.sh bash app/scripts/e2e-run-session.sh test/e2e/specs/mega-flow.spec.ts mega-flow
# Artifact uploads intentionally omitted — see e2e-linux for the
# reusable-workflow-is-also-used-by-releases rationale.
e2e-windows:
- if: inputs.run_windows
+ if: inputs.run_windows && !inputs.full
name: E2E (Windows / Appium Chromium)
runs-on: windows-latest
timeout-minutes: 90
@@ -328,6 +524,7 @@ jobs:
with:
ref: ${{ inputs.ref }}
fetch-depth: 1
+ persist-credentials: false
submodules: recursive
- name: Install pnpm
@@ -368,7 +565,7 @@ jobs:
key: appium3-chromium-${{ runner.os }}-v1
- name: Install JS dependencies
- run: pnpm install --frozen-lockfile
+ run: bash scripts/ci-cancel-aware.sh pnpm install --frozen-lockfile
- name: Ensure .env exists for E2E build
shell: bash
@@ -380,26 +577,314 @@ jobs:
shell: bash
run: |
if ! command -v appium >/dev/null 2>&1; then
- npm install -g appium@3
+ bash scripts/ci-cancel-aware.sh npm install -g appium@3
fi
# `appium driver list --installed` can miss cached installs on some
# Appium builds; install idempotently and ignore "already installed".
- appium driver install --source=npm appium-chromium-driver >/dev/null 2>&1 || true
+ bash scripts/ci-cancel-aware.sh appium driver install --source=npm appium-chromium-driver >/dev/null 2>&1 || true
- name: Build E2E app
- run: pnpm --filter openhuman-app test:e2e:build
+ run: bash scripts/ci-cancel-aware.sh pnpm --filter openhuman-app test:e2e:build
- - name: Run E2E (smoke + mega-flow)
- if: ${{ !inputs.full }}
+ - name: Run E2E (mega-flow)
shell: bash
run: |
- bash app/scripts/e2e-run-session.sh test/e2e/specs/smoke.spec.ts smoke
- bash app/scripts/e2e-run-session.sh test/e2e/specs/mega-flow.spec.ts mega-flow
-
- - name: Run E2E (full suite)
- if: ${{ inputs.full }}
- shell: bash
- run: bash app/scripts/e2e-run-session.sh
+ bash scripts/ci-cancel-aware.sh bash app/scripts/e2e-run-session.sh test/e2e/specs/mega-flow.spec.ts mega-flow
# Artifact uploads intentionally omitted — see e2e-linux for the
# reusable-workflow-is-also-used-by-releases rationale.
+
+ # ---------------------------------------------------------------------------
+ # Full-suite macOS — sharded matrix mirroring e2e-linux-full.
+ # ---------------------------------------------------------------------------
+ e2e-macos-full:
+ if: inputs.run_macos && inputs.full
+ name: E2E (macOS full / ${{ matrix.shard.name }})
+ runs-on: macos-latest
+ timeout-minutes: 60
+ strategy:
+ fail-fast: false
+ matrix:
+ shard:
+ - { name: foundation, suites: "auth,navigation,system" }
+ - { name: chat, suites: "chat,skills,journeys" }
+ - { name: providers, suites: "providers,notifications" }
+ - { name: webhooks, suites: "webhooks" }
+ - { name: connectors, suites: "connectors" }
+ - { name: commerce, suites: "payments,settings" }
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v5
+ with:
+ ref: ${{ inputs.ref }}
+ fetch-depth: 1
+ submodules: recursive
+
+ - name: Install pnpm
+ uses: pnpm/action-setup@v4
+
+ - name: Setup Node.js 24.x
+ uses: actions/setup-node@v5
+ with:
+ node-version: 24.x
+ cache: pnpm
+
+ - name: Install Rust (rust-toolchain.toml)
+ uses: dtolnay/rust-toolchain@1.93.0
+ with:
+ # macos-latest is arm64, but the vendored tauri-cli's build.rs
+ # compiles a CEF helper for x86_64-apple-darwin (universal binary),
+ # so the x86_64 libstd must be installed too. Without this the
+ # build fails with E0463 "can't find crate for `core`".
+ targets: x86_64-apple-darwin
+
+ - name: Verify cargo resolves to real toolchain
+ run: |
+ rustup default 1.93.0 || true
+ which cargo
+ cargo --version
+
+ - name: Cache Rust build artifacts
+ uses: Swatinem/rust-cache@v2
+ with:
+ workspaces: |
+ . -> target
+ app/src-tauri -> target
+ cache-on-failure: true
+ key: e2e-macos-unified-v2
+
+ - name: Cache CEF binary distribution
+ uses: actions/cache@v5
+ with:
+ path: |
+ ~/Library/Caches/tauri-cef
+ key: cef-aarch64-apple-darwin-${{ hashFiles('app/src-tauri/Cargo.toml') }}
+ restore-keys: |
+ cef-aarch64-apple-darwin-
+
+ - name: Cache Appium global install
+ uses: actions/cache@v5
+ with:
+ path: |
+ ~/.appium
+ key: appium3-chromium-${{ runner.os }}-v1
+
+ - name: Install JS dependencies
+ run: bash scripts/ci-cancel-aware.sh pnpm install --frozen-lockfile
+
+ - name: Ensure .env exists for E2E build
+ run: |
+ touch .env
+ touch app/.env
+
+ - name: Install Appium and chromium driver
+ run: |
+ if ! command -v appium >/dev/null 2>&1; then
+ bash scripts/ci-cancel-aware.sh npm install -g appium@3
+ fi
+ bash scripts/ci-cancel-aware.sh appium driver install --source=npm appium-chromium-driver >/dev/null 2>&1 || true
+
+ # Binary cache — see Linux full job for the rationale. Mac caches the
+ # entire .app bundle (self-contained including frontend assets + CEF
+ # Frameworks/OpenHuman Helper.app embedded by tauri-bundler).
+ - name: Cache built E2E binary (macOS)
+ id: e2e-binary-cache
+ uses: actions/cache@v5
+ with:
+ path: |
+ app/src-tauri/target/debug/bundle/macos/OpenHuman.app
+ key: e2e-binary-${{ runner.os }}-${{ hashFiles('src/**/*.rs', 'app/src-tauri/src/**', 'app/src-tauri/build.rs', 'app/src-tauri/tauri.conf.json', 'Cargo.lock', 'app/src-tauri/Cargo.lock', 'app/src-tauri/vendor/tauri-cef/Cargo.lock', 'rust-toolchain.toml', 'app/src/**', 'app/index.html', 'app/vite.config.*', 'app/tailwind.config.*', 'app/postcss.config.*', 'app/package.json', 'pnpm-lock.yaml', 'app/scripts/e2e-build.sh') }}
+
+ - name: Build E2E app
+ if: steps.e2e-binary-cache.outputs.cache-hit != 'true'
+ run: bash scripts/ci-cancel-aware.sh pnpm --filter openhuman-app test:e2e:build
+
+ # Adhoc-sign runs unconditionally — codesign is idempotent and a
+ # restored .app bundle from cache also needs to be (re-)signed for
+ # macOS to load its dynamic frameworks on this runner.
+ - name: Adhoc-sign the .app bundle
+ run: |
+ codesign --force --deep --sign - \
+ app/src-tauri/target/debug/bundle/macos/OpenHuman.app
+ codesign --verify --deep --verbose=2 \
+ app/src-tauri/target/debug/bundle/macos/OpenHuman.app
+
+ - name: Run E2E shard (${{ matrix.shard.name }} — suites=${{ matrix.shard.suites }})
+ env:
+ E2E_BAIL_ON_FAILURE: ${{ vars.E2E_BAIL_ON_FAILURE || '' }}
+ run: |
+ BAIL_FLAG=""
+ if [[ "${E2E_BAIL_ON_FAILURE:-}" == "1" ]]; then
+ BAIL_FLAG="--bail"
+ fi
+ bash scripts/ci-cancel-aware.sh bash app/scripts/e2e-run-all-flows.sh --skip-preflight \
+ --suite=${{ matrix.shard.suites }} $BAIL_FLAG
+
+ - name: Upload E2E failure artifacts
+ if: failure()
+ uses: actions/upload-artifact@v5
+ with:
+ name: e2e-failure-logs-${{ runner.os }}-${{ matrix.shard.name }}-${{ github.run_id }}
+ path: |
+ ${{ runner.temp }}/openhuman-e2e-app-*.log
+ /tmp/openhuman-e2e-app-*.log
+ app/test/e2e/artifacts/
+ retention-days: 7
+ if-no-files-found: ignore
+
+ - name: Write job summary
+ if: always()
+ run: |
+ echo "## E2E Results (${{ runner.os }} / ${{ matrix.shard.name }})" >> $GITHUB_STEP_SUMMARY
+ echo "" >> $GITHUB_STEP_SUMMARY
+ if [ -f /tmp/e2e-summary.txt ]; then
+ cat /tmp/e2e-summary.txt >> $GITHUB_STEP_SUMMARY
+ else
+ echo "No summary file found." >> $GITHUB_STEP_SUMMARY
+ fi
+
+ # ---------------------------------------------------------------------------
+ # Full-suite Windows — sharded matrix mirroring e2e-linux-full.
+ # ---------------------------------------------------------------------------
+ e2e-windows-full:
+ if: inputs.run_windows && inputs.full
+ name: E2E (Windows full / ${{ matrix.shard.name }})
+ runs-on: windows-latest
+ timeout-minutes: 60
+ strategy:
+ fail-fast: false
+ matrix:
+ shard:
+ - { name: foundation, suites: "auth,navigation,system" }
+ - { name: chat, suites: "chat,skills,journeys" }
+ - { name: providers, suites: "providers,notifications" }
+ - { name: webhooks, suites: "webhooks" }
+ - { name: connectors, suites: "connectors" }
+ - { name: commerce, suites: "payments,settings" }
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v5
+ with:
+ ref: ${{ inputs.ref }}
+ fetch-depth: 1
+ submodules: recursive
+
+ - name: Install pnpm
+ uses: pnpm/action-setup@v4
+
+ - name: Setup Node.js 24.x
+ uses: actions/setup-node@v5
+ with:
+ node-version: 24.x
+ cache: pnpm
+
+ - name: Install Rust (rust-toolchain.toml)
+ uses: dtolnay/rust-toolchain@1.93.0
+
+ - name: Cache Rust build artifacts
+ uses: Swatinem/rust-cache@v2
+ with:
+ workspaces: |
+ . -> target
+ app/src-tauri -> target
+ cache-on-failure: true
+ key: e2e-windows-unified
+
+ - name: Cache CEF binary distribution
+ id: cef-cache
+ uses: actions/cache@v5
+ with:
+ # ensure-tauri-cli.sh + e2e-build.sh both export
+ # CEF_PATH=$HOME/Library/Caches/tauri-cef regardless of OS; on
+ # Windows under Git Bash that resolves under the user profile and
+ # is the actual download target.
+ path: |
+ ~/Library/Caches/tauri-cef
+ key: cef-x86_64-pc-windows-msvc-v2-${{ hashFiles('app/src-tauri/Cargo.toml') }}
+ restore-keys: |
+ cef-x86_64-pc-windows-msvc-v2-
+
+ - name: Cache Appium global install
+ uses: actions/cache@v5
+ with:
+ path: |
+ ~/.appium
+ key: appium3-chromium-${{ runner.os }}-v1
+
+ - name: Install JS dependencies
+ run: bash scripts/ci-cancel-aware.sh pnpm install --frozen-lockfile
+
+ - name: Ensure .env exists for E2E build
+ shell: bash
+ run: |
+ touch .env
+ touch app/.env
+
+ - name: Install Appium and chromium driver
+ shell: bash
+ run: |
+ if ! command -v appium >/dev/null 2>&1; then
+ bash scripts/ci-cancel-aware.sh npm install -g appium@3
+ fi
+ bash scripts/ci-cancel-aware.sh appium driver install --source=npm appium-chromium-driver >/dev/null 2>&1 || true
+
+ # Binary cache — see Linux full job for rationale. Windows is built
+ # with --debug --no-bundle so the .exe + frontend dist are what the
+ # runner needs at launch. CEF runtime DLLs come from the dedicated
+ # CEF cache step above (now correctly pointing at the actual download
+ # location, ~/Library/Caches/tauri-cef).
+ - name: Cache built E2E binary (Windows)
+ id: e2e-binary-cache
+ uses: actions/cache@v5
+ with:
+ path: |
+ app/src-tauri/target/debug/OpenHuman.exe
+ app/dist
+ key: e2e-binary-${{ runner.os }}-${{ hashFiles('src/**/*.rs', 'app/src-tauri/src/**', 'app/src-tauri/build.rs', 'app/src-tauri/tauri.conf.json', 'Cargo.lock', 'app/src-tauri/Cargo.lock', 'app/src-tauri/vendor/tauri-cef/Cargo.lock', 'rust-toolchain.toml', 'app/src/**', 'app/index.html', 'app/vite.config.*', 'app/tailwind.config.*', 'app/postcss.config.*', 'app/package.json', 'pnpm-lock.yaml', 'app/scripts/e2e-build.sh') }}
+
+ # Skip the build only when BOTH the binary AND the CEF runtime caches
+ # hit (see Linux full job for the rationale).
+ - name: Build E2E app
+ if: steps.e2e-binary-cache.outputs.cache-hit != 'true' || steps.cef-cache.outputs.cache-hit != 'true'
+ run: bash scripts/ci-cancel-aware.sh pnpm --filter openhuman-app test:e2e:build
+
+ - name: Run E2E shard (${{ matrix.shard.name }} — suites=${{ matrix.shard.suites }})
+ shell: bash
+ env:
+ E2E_BAIL_ON_FAILURE: ${{ vars.E2E_BAIL_ON_FAILURE || '' }}
+ run: |
+ # See Linux shard — binary cache can skip the build that would have
+ # exported CEF_PATH. e2e-build.sh + ensure-tauri-cli.sh always
+ # download CEF to $HOME/Library/Caches/tauri-cef regardless of OS.
+ export CEF_PATH="$HOME/Library/Caches/tauri-cef"
+ BAIL_FLAG=""
+ if [[ "${E2E_BAIL_ON_FAILURE:-}" == "1" ]]; then
+ BAIL_FLAG="--bail"
+ fi
+ bash scripts/ci-cancel-aware.sh bash app/scripts/e2e-run-all-flows.sh --skip-preflight \
+ --suite=${{ matrix.shard.suites }} $BAIL_FLAG
+
+ - name: Upload E2E failure artifacts
+ if: failure()
+ uses: actions/upload-artifact@v5
+ with:
+ name: e2e-failure-logs-${{ runner.os }}-${{ matrix.shard.name }}-${{ github.run_id }}
+ # e2e-run-session.sh writes its app log to `${RUNNER_TEMP:-${TMPDIR:-/tmp}}`.
+ # On Windows runners RUNNER_TEMP resolves to D:\a\_temp, not /tmp.
+ path: |
+ ${{ runner.temp }}/openhuman-e2e-app-*.log
+ app/test/e2e/artifacts/
+ retention-days: 7
+ if-no-files-found: ignore
+
+ - name: Write job summary
+ if: always()
+ shell: bash
+ run: |
+ echo "## E2E Results (${{ runner.os }} / ${{ matrix.shard.name }})" >> $GITHUB_STEP_SUMMARY
+ echo "" >> $GITHUB_STEP_SUMMARY
+ if [ -f /tmp/e2e-summary.txt ]; then
+ cat /tmp/e2e-summary.txt >> $GITHUB_STEP_SUMMARY
+ else
+ echo "No summary file found." >> $GITHUB_STEP_SUMMARY
+ fi
diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml
index 8015e035f5..3ff23c9255 100644
--- a/.github/workflows/e2e.yml
+++ b/.github/workflows/e2e.yml
@@ -1,36 +1,20 @@
---
# PR/push E2E gate.
#
-# Calls the reusable `e2e-reusable.yml` with Linux only (smoke + mega-flow).
-# macOS / Windows E2E only runs at release time — see release-staging.yml
-# and release-production.yml `pretest` jobs which call the same reusable
-# workflow with `run_macos`, `run_windows`, and `full` all true.
-#
-# `workflow_dispatch` lets an operator opt in to a full-suite all-OS run
-# without cutting a release tag.
+# Desktop full-flow lane (mega-flow) across Linux, macOS, and Windows.
+# The browser-hosted Playwright suite lives in its own standalone workflow so
+# it can be run on demand without gating every push / PR.
name: E2E
on:
push:
branches: [main]
pull_request:
- workflow_dispatch:
- inputs:
- run_macos:
- description: Also run the macOS E2E job.
- type: boolean
- default: false
- run_windows:
- description: Also run the Windows E2E job.
- type: boolean
- default: false
- full:
- description: Run the entire spec suite (slow; ~30+ min per OS).
- type: boolean
- default: false
+ workflow_dispatch: {}
permissions:
contents: read
+ packages: read
pull-requests: read
concurrency:
@@ -38,10 +22,10 @@ concurrency:
cancel-in-progress: true
jobs:
- e2e:
+ e2e-desktop:
uses: ./.github/workflows/e2e-reusable.yml
with:
run_linux: true
- run_macos: ${{ github.event_name == 'workflow_dispatch' && inputs.run_macos }}
- run_windows: ${{ github.event_name == 'workflow_dispatch' && inputs.run_windows }}
- full: ${{ github.event_name == 'workflow_dispatch' && inputs.full }}
+ run_macos: true
+ run_windows: true
+ full: false
diff --git a/.github/workflows/ios-compile.yml b/.github/workflows/ios-compile.yml
new file mode 100644
index 0000000000..3c405e4490
--- /dev/null
+++ b/.github/workflows/ios-compile.yml
@@ -0,0 +1,93 @@
+---
+name: iOS Compile Sanity
+
+on:
+ pull_request:
+ paths:
+ - 'app/src-tauri-mobile/**'
+ - 'packages/tauri-plugin-ptt/**'
+ - 'src/openhuman/devices/**'
+ - 'app/src/services/transport/**'
+ - 'app/src/lib/tunnel/**'
+ - 'app/src/pages/ios/**'
+ - '.github/workflows/ios-compile.yml'
+ workflow_dispatch:
+
+permissions:
+ contents: read
+ pull-requests: read
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.head_ref || github.ref }}
+ cancel-in-progress: true
+
+jobs:
+ ios-compile:
+ name: iOS Compile Check
+ runs-on: macos-latest
+ env:
+ # Pin the deployment target so swift-rs invokes the Swift compiler with
+ # `-target arm64-apple-ios16.0`. Matches Package.swift in
+ # packages/tauri-plugin-ptt/ios/, which uses iOS 14+ APIs (OSLog).
+ IPHONEOS_DEPLOYMENT_TARGET: '16.0'
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 1
+ # The mobile crate uses stock Tauri (no CEF), so we don't need
+ # `submodules: recursive` — which would try to clone the
+ # `app/src-tauri/vendor/tauri-cef` submodule, a step that
+ # intermittently fails on macOS runners for fork PRs.
+ submodules: false
+
+ - name: Set up Rust
+ uses: dtolnay/rust-toolchain@stable
+ with:
+ toolchain: '1.93.0'
+ targets: aarch64-apple-ios
+
+ - name: Cache Rust build artifacts
+ uses: Swatinem/rust-cache@v2
+ with:
+ workspaces: |
+ . -> target
+ app/src-tauri-mobile -> target
+ packages/tauri-plugin-ptt -> target
+ cache-on-failure: true
+
+ - name: Set up pnpm
+ uses: pnpm/action-setup@v4
+
+ - name: Set up Node
+ uses: actions/setup-node@v4
+ with:
+ node-version: '24'
+ cache: 'pnpm'
+
+ - name: Install dependencies
+ run: bash scripts/ci-cancel-aware.sh pnpm install --frozen-lockfile
+
+ # Hard gate: mobile Tauri host compiles for iOS. No more soft-gate
+ # `continue-on-error` — the mobile crate uses stock Tauri without CEF
+ # so cef-dll-sys is not in the dependency graph.
+ - name: cargo check -- mobile host (aarch64-apple-ios)
+ run: bash scripts/ci-cancel-aware.sh cargo check --manifest-path app/src-tauri-mobile/Cargo.toml --target aarch64-apple-ios
+
+ # Hard gate: PTT plugin (host-target check; Swift sources are built
+ # lazily by swift-rs during the iOS-target check above).
+ - name: cargo check -- tauri-plugin-ptt
+ run: bash scripts/ci-cancel-aware.sh cargo check --manifest-path packages/tauri-plugin-ptt/Cargo.toml
+
+ # Hard gate: TypeScript compile.
+ - name: pnpm compile
+ run: bash scripts/ci-cancel-aware.sh pnpm --dir app compile
+
+ # Hard gate: iOS-relevant Vitest suites.
+ - name: pnpm test (iOS suites)
+ run: >
+ bash scripts/ci-cancel-aware.sh pnpm --dir app test --
+ src/services/transport
+ src/lib/tunnel
+ src/pages/ios
+ src/components/settings/panels/devices
diff --git a/.github/workflows/pr-quality.yml b/.github/workflows/pr-quality.yml
index 3099f524f8..061ea56152 100644
--- a/.github/workflows/pr-quality.yml
+++ b/.github/workflows/pr-quality.yml
@@ -5,6 +5,7 @@ on:
types: [opened, synchronize, reopened, labeled, unlabeled, edited]
permissions:
contents: read
+ packages: read
pull-requests: read
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.head_ref || github.ref }}
diff --git a/.github/workflows/release-packages.yml b/.github/workflows/release-packages.yml
index 3fe8d5931f..7cc7e5357d 100644
--- a/.github/workflows/release-packages.yml
+++ b/.github/workflows/release-packages.yml
@@ -76,7 +76,7 @@ jobs:
OPENHUMAN_BUILD_SHA: ${{ github.sha }}
OPENHUMAN_APP_ENV: production
run: |
- cargo build --release --bin openhuman-core
+ bash scripts/ci-cancel-aware.sh cargo build --release --bin openhuman-core
VERSION="${{ github.event.release.tag_name }}"
bash scripts/release/package-cli-tarball.sh \
target/release/openhuman-core \
diff --git a/.github/workflows/release-production.yml b/.github/workflows/release-production.yml
index 33dba820f5..256e704ac3 100644
--- a/.github/workflows/release-production.yml
+++ b/.github/workflows/release-production.yml
@@ -28,16 +28,23 @@ on:
default: patch
type: choice
options: [patch, minor, major]
- skip_e2e:
+ skip_pretests:
description:
- Skip the entire pretest phase (unit/rust plus E2E) and continue
- directly to create-release + build matrix. Use only when the
- required pretest signal is already known (e.g. promoting a
- staging tag whose pretests already ran green) and you need to
- unblock a production cut.
+ Skip the unit/rust pretest phase and continue directly to the build
+ matrix. Release workflows no longer run E2E. Use only when the
+ required pretest signal is already known and you need to unblock a
+ production cut.
required: false
type: boolean
default: false
+ create_release:
+ description:
+ Create and publish the GitHub Release and attach release assets. When
+ false, run the production build matrix without creating or publishing
+ a GitHub Release.
+ required: false
+ type: boolean
+ default: true
permissions:
contents: write
packages: write
@@ -52,9 +59,8 @@ concurrency:
# prepare-build
# │
# ├─── pretest-tests (reusable test-reusable.yml — unit + rust)
-# ├─── pretest-e2e (reusable e2e-reusable.yml — all 3 OS, full)
# │
-# ├─── create-release
+# ├─── create-release (optional no-op when `create_release=false`)
# │ │
# │ ┌────┴───────────────┬────────────────┐
# │ │ │ │
@@ -309,8 +315,7 @@ jobs:
echo "base_url=$BASE_URL" >> "$GITHUB_OUTPUT"
# =========================================================================
- # Phase 1b: Pretest gate — run the full test + E2E suite across every
- # target OS exactly once on the build ref before we spin up the release
+ # Phase 1b: Pretest gate — run unit + rust on the build ref before we spin up the release
# draft or any signed-build matrix. Pretest failures abort the workflow
# before `create-release` runs, so a busted commit never produces a
# half-finished GH Release that has to be cleaned up.
@@ -318,42 +323,35 @@ jobs:
pretest-tests:
name: Pretest — unit + rust
needs: [prepare-build]
- if: ${{ !inputs.skip_e2e }}
+ if: ${{ !inputs.skip_pretests }}
uses: ./.github/workflows/test-reusable.yml
with:
ref: ${{ needs.prepare-build.outputs.build_ref }}
- pretest-e2e:
- name: Pretest — E2E (all OS, full suite)
- needs: [prepare-build]
- if: ${{ !inputs.skip_e2e }}
- uses: ./.github/workflows/e2e-reusable.yml
- with:
- ref: ${{ needs.prepare-build.outputs.build_ref }}
- run_linux: true
- run_macos: true
- run_windows: true
- full: true
-
# =========================================================================
# Phase 2: Create draft GitHub release
# =========================================================================
create-release:
- name: Create GitHub release
+ name: Prepare GitHub release
runs-on: ubuntu-latest
environment: Production
- needs: [prepare-build, pretest-tests, pretest-e2e]
+ needs: [prepare-build, pretest-tests]
if: >-
always()
&& needs.prepare-build.result == 'success'
&& (needs.pretest-tests.result == 'success'
- || (inputs.skip_e2e && needs.pretest-tests.result == 'skipped'))
- && (needs.pretest-e2e.result == 'success'
- || (inputs.skip_e2e && needs.pretest-e2e.result == 'skipped'))
+ || (inputs.skip_pretests && needs.pretest-tests.result == 'skipped'))
outputs:
- release_id: ${{ steps.create.outputs.release_id }}
- upload_url: ${{ steps.create.outputs.upload_url }}
+ release_id: ${{ steps.create.outputs.release_id || steps.noop.outputs.release_id }}
+ upload_url: ${{ steps.create.outputs.upload_url || steps.noop.outputs.upload_url }}
steps:
+ - name: Skip release creation
+ if: ${{ !inputs.create_release }}
+ id: noop
+ run: |
+ echo "release_id=" >> "$GITHUB_OUTPUT"
+ echo "upload_url=" >> "$GITHUB_OUTPUT"
- name: Create draft release with generated notes
+ if: ${{ inputs.create_release }}
id: create
uses: actions/github-script@v8
with:
@@ -390,7 +388,7 @@ jobs:
build-desktop:
name: Build desktop matrix
needs: [prepare-build, create-release]
- # `always()` is load-bearing: when `skip_e2e=true` the pretest jobs are
+ # `always()` is load-bearing: when `skip_pretests=true` the pretest job is
# `skipped`, and GitHub propagates that skipped status transitively to any
# downstream job lacking an explicit status function — even though we only
# `needs` create-release here, the build would otherwise be skipped along
@@ -412,10 +410,10 @@ jobs:
telegram_bot_username: openhumanaibot
# with_macos_signing defaults to true — left implicit; production
# always notarizes. See build-desktop.yml inputs.
- with_release_upload: true
+ with_release_upload: ${{ inputs.create_release }}
release_id: ${{ needs.create-release.outputs.release_id }}
build_sidecar: false
- skip_pretests: ${{ inputs.skip_e2e }}
+ skip_pretests: ${{ inputs.skip_pretests }}
# =========================================================================
# Phase 3b: Build & push Docker image (runs parallel with build-desktop).
@@ -514,7 +512,7 @@ jobs:
build-cli-linux:
name: "CLI: ${{ matrix.target }}"
needs: [prepare-build, create-release]
- if: always() && needs.create-release.result == 'success'
+ if: ${{ inputs.create_release && always() && needs.create-release.result == 'success' }}
environment: Production
runs-on: ${{ matrix.runner }}
strategy:
@@ -561,7 +559,7 @@ jobs:
# baked elsewhere — see prepare-build.outputs.short_sha comment.
OPENHUMAN_BUILD_SHA: ${{ needs.prepare-build.outputs.short_sha }}
OPENHUMAN_APP_ENV: production
- run: cargo build --release --bin openhuman-core
+ run: bash scripts/ci-cancel-aware.sh cargo build --release --bin openhuman-core
- name: Package and upload tarball to release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -583,7 +581,7 @@ jobs:
publish-updater-manifest:
name: Publish updater manifest (latest.json)
needs: [prepare-build, create-release, build-desktop]
- if: always() && needs.build-desktop.result == 'success'
+ if: ${{ inputs.create_release && always() && needs.build-desktop.result == 'success' }}
runs-on: ubuntu-latest
environment: Production
steps:
@@ -616,6 +614,8 @@ jobs:
- build-docker
- publish-updater-manifest
if: >-
+ inputs.create_release
+ &&
always()
&& needs.build-desktop.result == 'success'
&& needs.build-cli-linux.result == 'success'
@@ -668,6 +668,10 @@ jobs:
// Linux desktop installer consumed by scripts/install.sh and
// advertised in latest.json as linux-x86_64.
/OpenHuman_.*_amd64\.AppImage$/,
+ // Linux arm64 desktop installer consumed by scripts/install.sh
+ // and advertised in latest.json as linux-aarch64.
+ /OpenHuman_.*_(arm64|aarch64)\.AppImage$/,
+ /OpenHuman_.*_(arm64|aarch64)\.deb$/,
// Auto-updater manifest — without this, installed clients can't
// discover new releases via plugins.updater.endpoints.
/^latest\.json$/,
@@ -716,7 +720,7 @@ jobs:
runs-on: ubuntu-latest
environment: Production
needs: [prepare-build, publish-release]
- if: always() && needs.publish-release.result == 'success'
+ if: ${{ inputs.create_release && always() && needs.publish-release.result == 'success' }}
env:
REGISTRY: ghcr.io
IMAGE_NAME: tinyhumansai/openhuman-core
@@ -752,7 +756,7 @@ jobs:
runs-on: ubuntu-latest
environment: Production
needs: [prepare-build, publish-release]
- if: always() && needs.publish-release.result == 'success'
+ if: ${{ inputs.create_release && always() && needs.publish-release.result == 'success' }}
env:
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
SENTRY_URL: ${{ vars.SENTRY_URL }}
@@ -792,7 +796,6 @@ jobs:
needs:
- prepare-build
- pretest-tests
- - pretest-e2e
- create-release
- build-desktop
- build-cli-linux
@@ -812,7 +815,7 @@ jobs:
&& (
(needs.create-release.result != 'success'
&& (needs.pretest-tests.result == 'failure' || needs.pretest-tests.result == 'cancelled'
- || needs.pretest-e2e.result == 'failure' || needs.pretest-e2e.result == 'cancelled'))
+ ))
|| (needs.create-release.result == 'success'
&& (needs.build-desktop.result == 'failure' || needs.build-desktop.result == 'cancelled'
|| needs.build-cli-linux.result == 'failure' || needs.build-cli-linux.result == 'cancelled'
@@ -823,7 +826,7 @@ jobs:
- name: Delete GitHub release
# Skip on the pretest-failure cleanup path: create-release didn't run,
# so there's no draft release to delete (only an orphaned tag).
- if: needs.create-release.result == 'success'
+ if: ${{ inputs.create_release && needs.create-release.result == 'success' }}
uses: actions/github-script@v8
with:
script: |
diff --git a/.github/workflows/release-staging.yml b/.github/workflows/release-staging.yml
index cb8b794b1a..3404661e50 100644
--- a/.github/workflows/release-staging.yml
+++ b/.github/workflows/release-staging.yml
@@ -3,12 +3,12 @@ name: Release Staging
on:
workflow_dispatch:
inputs:
- skip_e2e:
+ skip_pretests:
description:
- Skip the entire pretest phase (unit/rust plus E2E) and continue
- directly to the desktop/docker staging build. Use only when the
- required pretest signal is already known and you need to unblock a
- staging cut.
+ Skip the unit/rust pretest phase and continue directly to the
+ desktop/docker staging build. Release workflows no longer run E2E.
+ Use only when the required pretest signal is already known and you
+ need to unblock a staging cut.
required: false
type: boolean
default: false
@@ -26,9 +26,8 @@ concurrency:
# prepare-build
# │
# ├── pretest-tests (reusable test-reusable.yml — unit + rust;
-# │ optional when `skip_e2e` is true)
-# ├── pretest-e2e (reusable e2e-reusable.yml — all 3 OS, full suite;
-# │ optional when `skip_e2e` is true)
+# │ optional when `skip_pretests` is true)
+# ├── pretest-tests (reusable test-reusable.yml — unit + rust)
# │
# ├── build-desktop (delegated to .github/workflows/build-desktop.yml)
# ├── build-docker (build only — no GHCR push on staging)
@@ -37,10 +36,8 @@ concurrency:
# │
# cleanup-failed-staging (on failure)
#
-# The pretest jobs are a hard gate — `build-desktop` and `build-docker`
-# only start once unit/rust/E2E have all passed across every target. This
-# guarantees we never produce a staging tag whose installers were built
-# against unproven code.
+# The pretest job is a hard gate — `build-desktop` and `build-docker`
+# only start once unit/rust have passed, unless explicitly skipped.
#
# The actual desktop build / Sentry / artifact-upload pipeline lives in
# `.github/workflows/build-desktop.yml` and is shared with
@@ -189,42 +186,27 @@ jobs:
echo "base_url=https://staging-api.tinyhumans.ai/" >> "$GITHUB_OUTPUT"
# =========================================================================
- # Phase 1b: Pretest gate — run the full test + E2E suite across every
- # target OS exactly once on the staging commit before any build job
+ # Phase 1b: Pretest gate — run unit + rust once on the staging commit before any build job
# spins up. A failure here aborts the matrix (and `cleanup-failed-staging`
# deletes the tag) without burning four signed Tauri builds first.
# =========================================================================
pretest-tests:
name: Pretest — unit + rust
needs: [prepare-build]
- if: ${{ !inputs.skip_e2e }}
+ if: ${{ !inputs.skip_pretests }}
uses: ./.github/workflows/test-reusable.yml
with:
ref: ${{ needs.prepare-build.outputs.build_ref }}
- pretest-e2e:
- name: Pretest — E2E (all OS, full suite)
- needs: [prepare-build]
- if: ${{ !inputs.skip_e2e }}
- uses: ./.github/workflows/e2e-reusable.yml
- with:
- ref: ${{ needs.prepare-build.outputs.build_ref }}
- run_linux: true
- run_macos: true
- run_windows: true
- full: true
-
# =========================================================================
# Phase 2: Build desktop artifacts (delegated to reusable workflow)
# =========================================================================
build-desktop:
name: Build desktop matrix
- needs: [prepare-build, pretest-tests, pretest-e2e]
+ needs: [prepare-build, pretest-tests]
if: >-
always()
&& (needs.pretest-tests.result == 'success'
- || (inputs.skip_e2e && needs.pretest-tests.result == 'skipped'))
- && (needs.pretest-e2e.result == 'success'
- || (inputs.skip_e2e && needs.pretest-e2e.result == 'skipped'))
+ || (inputs.skip_pretests && needs.pretest-tests.result == 'skipped'))
uses: ./.github/workflows/build-desktop.yml
secrets: inherit
with:
@@ -254,7 +236,7 @@ jobs:
# real consumer. Set `build_sidecar: true` to re-enable a per-platform
# CLI Actions artifact + its Sentry DIF upload for QA spot-checks.
build_sidecar: false
- skip_pretests: ${{ inputs.skip_e2e }}
+ skip_pretests: ${{ inputs.skip_pretests }}
# =========================================================================
# Phase 2b: Build the openhuman-core Docker image without pushing.
@@ -267,13 +249,11 @@ jobs:
# =========================================================================
build-docker:
name: "Docker: build (no push)"
- needs: [prepare-build, pretest-tests, pretest-e2e]
+ needs: [prepare-build, pretest-tests]
if: >-
always()
&& (needs.pretest-tests.result == 'success'
- || (inputs.skip_e2e && needs.pretest-tests.result == 'skipped'))
- && (needs.pretest-e2e.result == 'success'
- || (inputs.skip_e2e && needs.pretest-e2e.result == 'skipped'))
+ || (inputs.skip_pretests && needs.pretest-tests.result == 'skipped'))
runs-on: ubuntu-latest
environment: Production
steps:
@@ -354,12 +334,11 @@ jobs:
name: Remove staging tag if build failed
runs-on: ubuntu-latest
environment: Production
- needs: [prepare-build, pretest-tests, pretest-e2e, build-desktop, build-docker]
+ needs: [prepare-build, pretest-tests, build-desktop, build-docker]
if: >-
always()
&& needs.prepare-build.result == 'success'
&& (needs.pretest-tests.result == 'failure' || needs.pretest-tests.result == 'cancelled'
- || needs.pretest-e2e.result == 'failure' || needs.pretest-e2e.result == 'cancelled'
|| needs.build-desktop.result == 'failure' || needs.build-desktop.result == 'cancelled'
|| needs.build-docker.result == 'failure' || needs.build-docker.result == 'cancelled')
steps:
diff --git a/.github/workflows/test-reusable.yml b/.github/workflows/test-reusable.yml
index 2d39193808..29cc81c87d 100644
--- a/.github/workflows/test-reusable.yml
+++ b/.github/workflows/test-reusable.yml
@@ -34,6 +34,7 @@ on:
permissions:
contents: read
+ packages: read
jobs:
i18n-coverage:
@@ -56,9 +57,9 @@ jobs:
restore-keys: |
pnpm-store-${{ runner.os }}-
- name: Install dependencies
- run: pnpm install --frozen-lockfile
+ run: bash scripts/ci-cancel-aware.sh pnpm install --frozen-lockfile
- name: Verify i18n coverage (missing / extra / drifted / en.ts ↔ chunks)
- run: pnpm i18n:check
+ run: bash scripts/ci-cancel-aware.sh pnpm i18n:check
unit-tests:
if: inputs.run_unit
@@ -80,9 +81,9 @@ jobs:
restore-keys: |
pnpm-store-${{ runner.os }}-
- name: Install dependencies
- run: pnpm install --frozen-lockfile
+ run: bash scripts/ci-cancel-aware.sh pnpm install --frozen-lockfile
- name: Run tests with coverage
- run: pnpm test:coverage
+ run: bash scripts/ci-cancel-aware.sh pnpm test:coverage
env:
NODE_ENV: test
- name: Upload coverage reports
@@ -110,6 +111,7 @@ jobs:
with:
ref: ${{ inputs.ref }}
fetch-depth: 1
+ persist-credentials: false
submodules: recursive
- name: Cache Rust build artifacts
uses: Swatinem/rust-cache@v2
@@ -120,13 +122,13 @@ jobs:
- name: Install sccache
uses: mozilla-actions/sccache-action@v0.0.9
- name: Test core crate (openhuman)
- run: cargo test -p openhuman
+ run: bash scripts/ci-cancel-aware.sh cargo test -p openhuman
rust-core-tests-windows:
if: inputs.run_rust_core
name: Rust Core Tests (Windows — secrets ACL)
runs-on: windows-latest
- timeout-minutes: 20
+ timeout-minutes: 35
env:
CARGO_INCREMENTAL: '0'
SCCACHE_GHA_ENABLED: 'true'
@@ -137,6 +139,7 @@ jobs:
with:
ref: ${{ inputs.ref }}
fetch-depth: 1
+ persist-credentials: false
submodules: recursive
- name: Cache Rust build artifacts
uses: Swatinem/rust-cache@v2
@@ -147,10 +150,16 @@ jobs:
- name: Install sccache
uses: mozilla-actions/sccache-action@v0.0.9
- name: Run Windows-specific secrets tests
- # Runs the full security::secrets suite including all #[cfg(windows)]
- # tests: self-repair ACL path (OPENHUMAN-TAURI-GN), domain-qualified
- # icacls username, is_permission_error, repair_windows_acl.
- run: cargo test -p openhuman -- security::secrets --nocapture
+ # Runs the keyring::encrypted_store suite (keyring::encrypted_store::tests),
+ # which contains the #[cfg(windows)] tests:
+ # - self_repair_recovers_from_locked_key_file (OPENHUMAN-TAURI-GN)
+ # - self_repair_does_not_trigger_for_corrupt_file
+ # - is_permission_error_* (access denied, not-found, raw OS error 5)
+ # - qualify_windows_username_* (local, domain, case, empty env vars)
+ # Note: repair_windows_acl is a helper fn, not a test.
+ # security/secrets.rs is a one-line re-export with no tests of its own;
+ # the old filter (-- security::secrets) silently matched nothing.
+ run: bash scripts/ci-cancel-aware.sh cargo test -p openhuman -- keyring::encrypted_store --nocapture
rust-tauri-tests:
if: inputs.run_rust_tauri
@@ -169,6 +178,7 @@ jobs:
ref: ${{ inputs.ref }}
fetch-depth: 1
# Required for app/src-tauri/vendor/tauri-cef.
+ persist-credentials: false
submodules: recursive
- name: Cache Rust build artifacts
uses: Swatinem/rust-cache@v2
@@ -188,4 +198,4 @@ jobs:
- name: Install sccache
uses: mozilla-actions/sccache-action@v0.0.9
- name: Test Tauri shell (OpenHuman)
- run: cargo test --manifest-path app/src-tauri/Cargo.toml
+ run: bash scripts/ci-cancel-aware.sh cargo test --manifest-path app/src-tauri/Cargo.toml
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 56e5fdc1d5..71e8c4cd10 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -13,6 +13,7 @@ on:
permissions:
contents: read
+ packages: read
pull-requests: read
concurrency:
diff --git a/.github/workflows/typecheck.yml b/.github/workflows/typecheck.yml
index fc38a1b79e..dcc0344bd8 100644
--- a/.github/workflows/typecheck.yml
+++ b/.github/workflows/typecheck.yml
@@ -6,6 +6,7 @@ on:
pull_request:
permissions:
contents: read
+ packages: read
pull-requests: read
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.head_ref
diff --git a/.gitignore b/.gitignore
index fa01e8d694..c49daf3b8b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,6 +8,9 @@ remove_dupe_nonsense_issues
# Diagnostic harness output (scripts/diagnose-cef-runtime.mjs)
diagnosis-*.json
+# Dev-mode keyring file backend (plain-text secrets, dev artifact only)
+dev-keychain.json
+
# Logs
logs
*.log
@@ -103,3 +106,5 @@ test-map.md
# AI assistant progress tracking
.kimi/
+.codex-tmp
+*.enc
diff --git a/AGENTS.md b/AGENTS.md
index c45cf9b9a5..39e6809eaa 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -70,6 +70,11 @@ 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`:
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`).
@@ -525,6 +530,8 @@ Follow this order so behavior is **specified**, **proven in Rust**, **proven ove
- **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 chunk files — update ALL locales**: The source-of-truth translation files are the **chunk files** under `app/src/lib/i18n/chunks/` (`en-{1..5}.ts` plus `
- OpenHuman ist deine persönliche KI-Superintelligenz. Privat, schlicht und extrem mächtig. + OpenHuman ist deine persönliche KI-Superintelligenz: Lokaler Speicher, verwaltete Dienste wo nötig, schlicht und mächtig.
@@ -46,6 +46,8 @@ > **Frühe Beta**: Wird aktiv weiterentwickelt. Mit Ecken und Kanten ist zu rechnen. +> **Lokal + verwaltete Dienste, upfront:** OpenHuman speichert seinen Memory Tree, Obsidian-Style-Markdown-Vault, Workspace-Konfiguration und lokalen Laufzeitstatus auf deiner Maschine. Die standardmäßige verwaltete Erfahrung nutzt weiterhin OpenHuman-gehostete Dienste für Account-Anmeldung, Model-Routing, Web-Search-Proxying und verwaltete Integration/OAuth-Flows über die Composio-Connector-Schicht. Wähle benutzerdefinierte/lokale Einstellungen, wenn du dein eigenes Modell, deine eigene Suche oder Composio-Credentials mitbringen möchtest; einige Echtzeit-Trigger und gehostete Funktionen erfordern weiterhin das verwaltete Backend. + Für Installation und Einstieg lade die App von [tinyhumans.ai/openhuman](https://tinyhumans.ai/openhuman?utm_source=github&utm_medium=readme) herunter oder führe im Terminal aus: ```bash @@ -66,9 +68,11 @@ OpenHuman ist ein quelloffener, agentenbasierter Assistent, der sich in deinen A - **[118+ Drittanbieter-Integrationen](https://tinyhumans.gitbook.io/openhuman/features/integrations) mit [Auto-Fetch](https://tinyhumans.gitbook.io/openhuman/features/obsidian-wiki/auto-fetch)**: Gmail, Notion, GitHub, Slack, Stripe, Calendar, Drive, Linear, Jira und der Rest deines Stacks per **Ein-Klick-OAuth** anbinden. Jede Verbindung wird dem Agenten als typisiertes Tool freigegeben, und alle zwanzig Minuten geht der Core durch jede aktive Verbindung und zieht frische Daten in den [Memory Tree](https://tinyhumans.gitbook.io/openhuman/features/integrations/auto-fetch). Keine Prompts, keine Polling-Schleifen, die du selbst schreiben musst — der Agent hat morgens schon den Kontext für den Tag. + Verwaltete Integrationen nutzen OpenHumans Composio-Connector-Schicht. OAuth-Handshakes und Integration-Tool-Calls werden standardmäßig über das verwaltete Backend geproxied. Wenn du stattdessen Composio direkt betreiben möchtest, konfiguriere den Direktmodus mit deinem eigenen Composio-API-Key; Echtzeit-Trigger-Webhooks müssen dann von dir selbst gehostet und verkabelt werden. + - **[Memory Tree](https://tinyhumans.gitbook.io/openhuman/features/memory-tree) + [Obsidian-Wiki](https://tinyhumans.gitbook.io/openhuman/features/obsidian-wiki)**: eine lokal-zentrierte Wissensbasis, aufgebaut aus deinen Daten und deinen Aktivitäten. Alles, was du verbindest, wird in Markdown-Chunks von ≤3k Tokens kanonisiert, bewertet und in hierarchische Zusammenfassungs-Bäume gefaltet, gespeichert in **SQLite auf deiner Maschine**. Dieselben Chunks landen als `.md`-Dateien in einem Obsidian-kompatiblen Vault, das du öffnen, durchstöbern und editieren kannst — inspiriert von Karpathys [obsidian-wiki-Workflow](https://x.com/karpathy/status/2039805659525644595). -- **Alles eingebaut**: Web-Suche, ein Web-Fetch-[Scraper](https://tinyhumans.gitbook.io/openhuman/features/native-tools), ein vollständiges Coder-Toolset (Dateisystem, Git, Lint, Test, Grep) und [native Sprache](https://tinyhumans.gitbook.io/openhuman/features/voice) (STT als Eingabe, ElevenLabs TTS als Ausgabe, Lippensynchronisation für das Maskottchen, Live-Google-Meet-Agent) sind ab Werk verdrahtet. [Model-Routing](https://tinyhumans.gitbook.io/openhuman/features/model-routing) schickt jede Aufgabe an das passende LLM (Reasoning, Fast oder Vision) — alles unter einem Abo. Keine "erst-ein-Plugin-installieren-um-Dateien-zu-lesen"-Hürde. [Optional lokale KI über Ollama](https://tinyhumans.gitbook.io/openhuman/features/model-routing/local-ai) für On-Device-Workloads. +- **Alles eingebaut**: Web-Suche, ein Web-Fetch-[Scraper](https://tinyhumans.gitbook.io/openhuman/features/native-tools), ein vollständiges Coder-Toolset (Dateisystem, Git, Lint, Test, Grep) und [native Sprache](https://tinyhumans.gitbook.io/openhuman/features/voice) (STT als Eingabe, ElevenLabs TTS als Ausgabe, Lippensynchronisation für das Maskottchen, Live-Google-Meet-Agent) sind ab Werk verdrahtet. Standardmäßig nutzt [Model-Routing](https://tinyhumans.gitbook.io/openhuman/features/model-routing) das OpenHuman-Backend, um das passende LLM für jede Workload auszuwählen und zu proxien (Reasoning, Fast oder Vision). Ein Abo umfasst alle Modelle. Keine "erst-ein-Plugin-installieren-um-Dateien-zu-lesen"-Hürde. [Optional lokale KI über Ollama](https://tinyhumans.gitbook.io/openhuman/features/model-routing/local-ai) für On-Device-Workloads. - **[Smarte Token-Kompression (TokenJuice)](https://tinyhumans.gitbook.io/openhuman/features/token-compression)**: Jeder Tool-Aufruf, jedes Scrape-Ergebnis, jeder E-Mail-Text und jeder Such-Payload läuft durch eine Token-Kompressionsschicht, bevor er ein LLM-Modell erreicht. HTML wird zu Markdown konvertiert, lange URLs werden gekürzt, und ausschweifende Tool-Ausgaben werden über eine konfigurierbare Regel-Ebene dedupliziert und zusammengefasst usw. CJK, Emojis und andere Multi-Byte-Texte bleiben Graphem für Graphem erhalten — niemals abgeschnitten. Du erhältst dieselbe Information bei einem Bruchteil der Tokens. Kosten und Latenz sinken um bis zu 80%. diff --git a/README.ja-JP.md b/README.ja-JP.md index 1e74129cd6..06c007c159 100644 --- a/README.ja-JP.md +++ b/README.ja-JP.md @@ -16,7 +16,7 @@- OpenHuman はあなたのパーソナル AI スーパーインテリジェンスです。プライベートで、シンプルで、非常に強力。 + OpenHuman はあなたのパーソナル AI スーパーインテリジェンスです:ローカルメモリ、必要に応じてマネージドサービス、シンプルで強力。
@@ -46,6 +46,8 @@ > **早期ベータ版**: 現在も活発に開発中です。荒削りな部分があることをご了承ください。 +> **ローカル + マネージドサービス、upfront:** OpenHuman は Memory Tree、Obsidian スタイルの Markdown ヴォルト、ワークスペース設定、およびローカルランタイム状態をあなたのマシン上に保存します。デフォルトのマネージド体験では、アカウントサインイン、モデルルーティング、Web 検索プロキシ、および Composio コネクタレイヤーを介したマネージド統合/OAuth フローに、OpenHuman ホスト型サービスが引き続き使用されます。独自のモデル、検索、または Composio 認証情報を持ち込みたい場合は、カスタム/ローカル設定を選択してください。一部のリアルタイムトリガーおよびホスト型機能には、マネージドバックエンドが引き続き必要です。 + インストールや利用開始は、ウェブサイト [tinyhumans.ai/openhuman](https://tinyhumans.ai/openhuman?utm_source=github&utm_medium=readme) からダウンロードするか、以下のコマンドを実行してください。 ```bash @@ -66,9 +68,11 @@ OpenHuman は、あなたの日常生活に統合されるよう設計された - **[118+ のサードパーティ統合](https://tinyhumans.gitbook.io/openhuman/features/integrations) と [自動取得](https://tinyhumans.gitbook.io/openhuman/features/obsidian-wiki/auto-fetch)**: Gmail、Notion、GitHub、Slack、Stripe、Calendar、Drive、Linear、Jira などのスタックに **ワンクリック OAuth** で接続できます。すべての接続は型付きツールとしてエージェントに公開され、20 分ごとにコアがアクティブな各接続を巡回し、最新データを[メモリーツリー](https://tinyhumans.gitbook.io/openhuman/features/integrations/auto-fetch)に取り込みます。プロンプトも、自分で書くポーリングループも不要なので、エージェントは今朝の時点で明日のコンテキストを既に持っています。 + マネージド統合は OpenHuman の Composio コネクタレイヤーを使用します。OAuth ハンドシェイクおよび統合ツール呼び出しは、デフォルトでマネージドバックエンドを介してプロキシされます。代わりに Composio を直接実行したい場合は、独自の Composio API キーでダイレクトモードを構成してください。リアルタイムトリガーの Webhook は、その後あなたがホストして配線する必要があります。 + - **[Memory Tree](https://tinyhumans.gitbook.io/openhuman/features/memory-tree) + [Obsidian Wiki](https://tinyhumans.gitbook.io/openhuman/features/obsidian-wiki)**: あなたのデータとアクティビティから構築されるローカルファーストのナレッジベースです。接続したすべての情報は ≤3k トークンの Markdown チャンクへ正規化され、スコアリングされ、階層的なサマリーツリーに畳み込まれて **あなたのマシン上の SQLite** に保存されます。同じチャンクは Obsidian 互換のボルトに `.md` ファイルとして配置され、開いて閲覧・編集できます。Karpathy 氏の [obsidian-wiki ワークフロー](https://x.com/karpathy/status/2039805659525644595)にインスパイアされています。 -- **電池同梱(Batteries included)**: ウェブ検索、ウェブフェッチ用[スクレイパー](https://tinyhumans.gitbook.io/openhuman/features/native-tools)、フルコーダーツールセット(ファイルシステム、git、lint、test、grep)、そして[ネイティブ音声](https://tinyhumans.gitbook.io/openhuman/features/voice)(STT 入力、ElevenLabs TTS 出力、マスコットのリップシンク、ライブ Google Meet エージェント)がデフォルトで組み込まれています。[モデルルーティング](https://tinyhumans.gitbook.io/openhuman/features/model-routing)が各タスクを適切な LLM(reasoning、fast、または vision)に振り分け、一つのサブスクリプションで提供します。「ファイル読み込みのためにプラグインをインストール」という煩わしさはありません。デバイス上のワークロード向けに [Ollama によるオプショナルなローカル AI](https://tinyhumans.gitbook.io/openhuman/features/model-routing/local-ai) も利用できます。 +- **電池同梱(Batteries included)**: ウェブ検索、ウェブフェッチ用[スクレイパー](https://tinyhumans.gitbook.io/openhuman/features/native-tools)、フルコーダーツールセット(ファイルシステム、git、lint、test、grep)、そして[ネイティブ音声](https://tinyhumans.gitbook.io/openhuman/features/voice)(STT 入力、ElevenLabs TTS 出力、マスコットのリップシンク、ライブ Google Meet エージェント)がデフォルトで組み込まれています。デフォルトで、[モデルルーティング](https://tinyhumans.gitbook.io/openhuman/features/model-routing)は OpenHuman バックエンドを使用して各ワークロードに適切な LLM(reasoning、fast、または vision)を選択およびプロキシします。一つのサブスクリプションですべてのモデルが含まれます。「ファイル読み込みのためにプラグインをインストール」という煩わしさはありません。デバイス上のワークロード向けに [Ollama によるオプショナルなローカル AI](https://tinyhumans.gitbook.io/openhuman/features/model-routing/local-ai) も利用できます。 - **[スマートトークン圧縮 (TokenJuice)](https://tinyhumans.gitbook.io/openhuman/features/token-compression)**: すべてのツール呼び出し、スクレイプ結果、メール本文、検索ペイロードは、LLM モデルに渡される前にトークン圧縮レイヤーを通過します。HTML は Markdown に変換され、長い URL は短縮され、冗長なツール出力は設定可能なルールレイヤーで重複排除と要約が行われるなど…。CJK、絵文字などのマルチバイト文字は書記素(grapheme)単位で完全に保持され、除去されることはありません。同じ情報をわずかなトークン数で得られます。コストとレイテンシを最大 80% 削減します。 diff --git a/README.ko.md b/README.ko.md index b1cb3d9637..e1e3f6ed69 100644 --- a/README.ko.md +++ b/README.ko.md @@ -16,7 +16,7 @@- OpenHuman은 당신의 개인용 AI 슈퍼 지능입니다. 프라이빗하고, 단순하며, 매우 강력합니다. + OpenHuman은 당신의 개인용 AI 슈퍼 지능입니다: 로컬 메모리, 필요한 경우 관리형 서비스, 단순하고 강력합니다.
@@ -47,6 +47,8 @@ > **얼리 베타**: 활발히 개발 중입니다. 다소 미흡한 부분이 있을 수 있습니다. +> **로컬 + 관리형 서비스, upfront:** OpenHuman은 Memory Tree, Obsidian 스타일 Markdown 볼트, 워크스페이스 설정 및 로컬 런타임 상태를 사용자의 머신에 저장합니다. 기본 관리형 경험은 여전히 계정 로그인, 모델 라우팅, 웹 검색 프록시 및 Composio 커넥터 레이어를 통한 관리형 통합/OAuth 플로우에 OpenHuman 호스팅 서비스를 사용합니다. 자체 모델, 검색 또는 Composio 인증 정보를 가져오려면 사용자 지정/로컬 설정을 선택하세요. 일부 실시간 트리거 및 호스팅 기능은 여전히 관리형 백엔드를 필요로 합니다. + 설치하거나 시작하려면 [tinyhumans.ai/openhuman](https://tinyhumans.ai/openhuman?utm_source=github&utm_medium=readme) 웹사이트에서 다운로드하거나 터미널에서 다음을 실행하세요. ```bash @@ -69,7 +71,7 @@ OpenHuman은 일상 생활에 통합되도록 설계된 오픈 소스 에이전 - **[메모리 트리(Memory Tree)](https://tinyhumans.gitbook.io/openhuman/features/memory-tree) + [Obsidian 위키](https://tinyhumans.gitbook.io/openhuman/features/obsidian-wiki)**: 당신의 데이터와 활동을 바탕으로 구축된 로컬 우선 지식 베이스입니다. 연결된 모든 것은 3k 토큰 이하의 Markdown 청크로 규격화되고 점수가 매겨지며, **당신의 머신에 있는 SQLite**에 저장되는 계층적 요약 트리로 접힙니다. 동일한 청크는 당신이 열고, 탐색하고, 편집할 수 있는 Obsidian 호환 볼트에 `.md` 파일로 저장됩니다. 이는 Karpathy의 [obsidian-wiki 워크플로우](https://x.com/karpathy/status/2039805659525644595)에서 영감을 받았습니다. -- **모든 것이 포함됨(Batteries included)**: 웹 검색, 웹 가져오기 [스크레이퍼](https://tinyhumans.gitbook.io/openhuman/features/native-tools), 전체 코더 툴셋(파일 시스템, git, lint, test, grep), 그리고 [네이티브 음성](https://tinyhumans.gitbook.io/openhuman/features/voice)(STT 입력, ElevenLabs TTS 출력, 마스코트 립싱크, 라이브 Google Meet 에이전트)이 기본적으로 연결되어 있습니다. [모델 라우팅](https://tinyhumans.gitbook.io/openhuman/features/model-routing)은 단일 구독 하에 각 작업을 적절한 LLM(추론, 고속 또는 비전)으로 보냅니다. "파일을 읽기 위해 플러그인 설치"와 같은 번거로움이 없습니다. 온디바이스 워크로드를 위해 [Ollama를 통한 선택적 로컬 AI](https://tinyhumans.gitbook.io/openhuman/features/model-routing/local-ai)를 지원합니다. +- **모든 것이 포함됨(Batteries included)**: 웹 검색, 웹 가져오기 [스크레이퍼](https://tinyhumans.gitbook.io/openhuman/features/native-tools), 전체 코더 툴셋(파일 시스템, git, lint, test, grep), 그리고 [네이티브 음성](https://tinyhumans.gitbook.io/openhuman/features/voice)(STT 입력, ElevenLabs TTS 출력, 마스코트 립싱크, 라이브 Google Meet 에이전트)이 기본적으로 연결되어 있습니다. 기본적으로 [모델 라우팅](https://tinyhumans.gitbook.io/openhuman/features/model-routing)은 OpenHuman 백엔드를 사용하여 각 워크로드에 적합한 LLM(추론, 고속 또는 비전)을 선택하고 프록시합니다. 하나의 구독에 모든 모델이 포함됩니다. "파일을 읽기 위해 플러그인 설치"와 같은 번거로움이 없습니다. 온디바이스 워크로드를 위해 [Ollama를 통한 선택적 로컬 AI](https://tinyhumans.gitbook.io/openhuman/features/model-routing/local-ai)를 지원합니다. - **[스마트 토큰 압축(TokenJuice)](https://tinyhumans.gitbook.io/openhuman/features/token-compression)**: 모든 도구 호출, 스크레이핑 결과, 이메일 본문 및 검색 페이로드는 LLM 모델에 전달되기 전에 토큰 압축 레이어를 거칩니다. HTML은 Markdown으로 변환되고, 긴 URL은 단축되며, 장황한 도구 출력은 구성 가능한 규칙 오버레이 등을 통해 중복 제거 및 요약됩니다. CJK, 이모지 및 기타 멀티바이트 텍스트는 자소(grapheme) 단위로 보존되며 절대 삭제되지 않습니다. 동일한 정보를 훨씬 적은 토큰으로 얻을 수 있어 비용과 지연 시간을 최대 80%까지 줄일 수 있습니다. diff --git a/README.md b/README.md index f34d7a4c18..88b4019b19 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@- OpenHuman is your Personal AI super intelligence. Private, Simple and extremely powerful. + OpenHuman is your Personal AI super intelligence: local memory, managed services where needed, simple and powerful.
@@ -56,18 +56,60 @@ > **Early Beta**: Under active development. Expect rough edges. -To install or get started, either download from the website over at [tinyhumans.ai/openhuman](https://tinyhumans.ai/openhuman?utm_source=github&utm_medium=readme) or run +> **Local + managed services, upfront:** OpenHuman stores its Memory Tree, Obsidian-style Markdown vault, workspace config, and local runtime state on your machine. The default managed experience still uses OpenHuman-hosted services for account sign-in, model routing, web search proxying, and managed integration/OAuth flows through the Composio connector layer. Choose custom/local settings if you want to bring your own model, search, or Composio credentials; some real-time triggers and hosted features still require the managed backend. + +# Install + +Download installers from [tinyhumans.ai/openhuman](https://tinyhumans.ai/openhuman?utm_source=github&utm_medium=readme) or from the [GitHub Releases](https://github.com/tinyhumansai/openhuman/releases/latest) page. For terminal installs, the native package paths below are preferred — they ride your OS package-manager's signing chain. + +## Recommended install (native packages) + +These paths verify the artifact through your OS package manager's signing chain (Homebrew bottle hash, signed apt repo, MSI signature). + +**macOS (Homebrew tap):** ```bash -# Download DMG, EXEs over at https://tinyhumans.ai/openhuman or run in from your terminal +brew tap tinyhumansai/core +brew install openhuman +``` + +**Linux (Debian/Ubuntu — signed apt repo):** + +```bash +sudo apt-get install -y --no-install-recommends gnupg2 curl ca-certificates +curl -fsSL https://tinyhumansai.github.io/openhuman/apt/KEY.gpg \ + | sudo gpg --dearmor -o /etc/apt/keyrings/openhuman.gpg +echo "deb [signed-by=/etc/apt/keyrings/openhuman.gpg arch=amd64] \ + https://tinyhumansai.github.io/openhuman/apt stable main" \ + | sudo tee /etc/apt/sources.list.d/openhuman.list +sudo apt-get update +sudo apt-get install -y openhuman +``` -# For macOS or Linux x64 +**Linux (Arch — AUR):** the [`openhuman-bin` AUR recipe](./packages/arch/openhuman-bin/) is in the repo. Once published, Arch users can install it with `yay -S openhuman-bin`. + +**Windows:** download the signed `.msi` from the [latest release](https://github.com/tinyhumansai/openhuman/releases/latest) and run it. + +**Manual `.dmg` / `.deb` / `.AppImage` / `.msi`:** grab the installer for your platform directly from the [latest release page](https://github.com/tinyhumansai/openhuman/releases/latest). + +> **Linux:** the AppImage can crash on launch under Wayland (and on Arch-based distros with `sharun: Interpreter not found!`) — see [#2463](https://github.com/tinyhumansai/openhuman/issues/2463) for the cause and env-var workarounds. The `.deb` package above avoids those failure modes on Debian/Ubuntu. + +## Alternative: script install (no integrity check) + +> **Warning — unverified install.** These scripts are served live from `raw.githubusercontent.com` and do **not** ship a separate signature, so `curl … | bash` and `irm … | iex` have no way to detect tampering of the script bytes. Prefer the **native package** paths above whenever possible. If you must use the script, see "Verified script install" below. + +```bash +# macOS or Linux x64 curl -fsSL https://raw.githubusercontent.com/tinyhumansai/openhuman/main/scripts/install.sh | bash -# For Windows +# Windows (PowerShell) irm https://raw.githubusercontent.com/tinyhumansai/openhuman/main/scripts/install.ps1 | iex ``` +## Verified script install (coming soon) + +PR2 of [#2620](https://github.com/tinyhumansai/openhuman/issues/2620) will publish `install.sh.asc` / `install.ps1.asc` as release assets and document the `gpg --verify` (and Windows equivalent) flow here, so the script path can be made integrity-checked end-to-end. + # What is OpenHuman? OpenHuman is an open-source agentic assistant designed to integrate with you in your daily life. Each bullet links to the deeper writeup in the [docs](https://tinyhumans.gitbook.io/openhuman/). @@ -76,11 +118,11 @@ OpenHuman is an open-source agentic assistant designed to integrate with you in - **[118+ third-party integrations](https://tinyhumans.gitbook.io/openhuman/features/integrations) with [auto-fetch](https://tinyhumans.gitbook.io/openhuman/features/obsidian-wiki/auto-fetch)**: plug into Gmail, Notion, GitHub, Slack, Stripe, Calendar, Drive, Linear, Jira and the rest of your stack with **one-click OAuth**. Every connection is exposed to the agent as a typed tool, and every twenty minutes the core walks each active connection and pulls fresh data into the [memory tree](https://tinyhumans.gitbook.io/openhuman/features/integrations/auto-fetch). No prompts, no polling loops you have to write, so the agent already has tomorrow's context this morning. - Managed integrations are backend-proxied through OpenHuman's Composio connector layer. If you want to run Composio directly instead of using the managed backend path, configure direct mode with your own Composio API key; real-time trigger webhooks then need to be hosted and wired by you. + Managed integrations use OpenHuman's Composio connector layer. OAuth handshakes and integration tool calls are proxied through the managed backend by default. If you want to run Composio directly instead, configure direct mode with your own Composio API key; real-time trigger webhooks then need to be hosted and wired by you. - **[Memory Tree](https://tinyhumans.gitbook.io/openhuman/features/memory-tree) + [Obsidian Wiki](https://tinyhumans.gitbook.io/openhuman/features/obsidian-wiki)**: a local-first knowledge base built from your data and your activity. Everything you connect is canonicalized into ≤3k-token Markdown chunks, scored, and folded into hierarchical summary trees stored in **SQLite on your machine**. The same chunks land as `.md` files in an Obsidian-compatible vault you can open, browse and edit, inspired by Karpathy's [obsidian-wiki workflow](https://x.com/karpathy/status/2039805659525644595). -- **Batteries included**: web search, a web-fetch [scraper](https://tinyhumans.gitbook.io/openhuman/features/native-tools), a full coder toolset (filesystem, git, lint, test, grep), and [native voice](https://tinyhumans.gitbook.io/openhuman/features/voice) (STT in, ElevenLabs TTS out, mascot lip-sync, live Google Meet agent) are wired in by default. [Model routing](https://tinyhumans.gitbook.io/openhuman/features/model-routing) sends each task to the right LLM (reasoning, fast, or vision) under one subscription. No "install a plugin to read files" friction. [Optional local AI via Ollama](https://tinyhumans.gitbook.io/openhuman/features/model-routing/local-ai) for on-device workloads. +- **Batteries included**: web search, a web-fetch [scraper](https://tinyhumans.gitbook.io/openhuman/features/native-tools), a full coder toolset (filesystem, git, lint, test, grep), and [native voice](https://tinyhumans.gitbook.io/openhuman/features/voice) (STT in, ElevenLabs TTS out, mascot lip-sync, live Google Meet agent) are wired in by default. By default, [model routing](https://tinyhumans.gitbook.io/openhuman/features/model-routing) uses the OpenHuman backend to select and proxy the right LLM for each workload (reasoning, fast, or vision). One subscription includes all models. No "install a plugin to read files" friction. Use [optional local AI via Ollama](https://tinyhumans.gitbook.io/openhuman/features/model-routing/local-ai) for supported on-device workloads. - **[Smart token compression (TokenJuice)](https://tinyhumans.gitbook.io/openhuman/features/token-compression)**: every tool call, scrape result, email body, and search payload is run through a token compression layer before it touches any LLM Model. HTML is converted to Markdown, long URLs are shortened, and verbose tool output is deduped and summarized via a configurable rule overlay etc... CJK, emoji, and other multi-byte text are preserved grapheme-by-grapheme — never stripped. You get the same information but at a fraction of the tokens. Reducing cost & latency by up to 80%. diff --git a/README.zh-CN.md b/README.zh-CN.md index 5a882f7289..ff939b96e1 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -20,7 +20,7 @@- OpenHuman 是你的个人 AI 超级智能。私密、简洁、极其强大。 + OpenHuman 是你的个人 AI 超级智能:本地记忆,按需托管服务,简洁而强大。
@@ -45,6 +45,8 @@ > **早期测试版**:正在积极开发中,可能存在不完善之处。 +> **本地 + 托管服务,upfront:** OpenHuman 将记忆树、Obsidian 风格 Markdown 仓库、工作区配置和本地运行时状态存储在你的机器上。默认的托管体验仍然使用 OpenHuman 托管服务进行账户登录、模型路由、网页搜索代理,以及通过 Composio 连接器层的托管集成/OAuth 流程。如果你想自带模型、搜索或 Composio 凭据,请选择自定义/本地设置;某些实时触发器和托管功能仍然需要托管后端。 + 要安装或开始使用,请从 [tinyhumans.ai/openhuman](https://tinyhumans.ai/openhuman?utm_source=github&utm_medium=readme) 下载,或在终端中运行: ```bash @@ -65,9 +67,11 @@ OpenHuman 是一个开源智能助手,旨在融入你的日常生活。以下 - **[118+ 第三方集成](https://tinyhumans.gitbook.io/openhuman/features/integrations) + [自动拉取](https://tinyhumans.gitbook.io/openhuman/features/obsidian-wiki/auto-fetch)**:通过**一键 OAuth** 接入 Gmail、Notion、GitHub、Slack、Stripe、Calendar、Drive、Linear、Jira 以及你技术栈中的其他服务。每个连接都以类型化工具的形式暴露给智能体,核心每 20 分钟遍历每个活跃连接并将新数据拉入[记忆树](https://tinyhumans.gitbook.io/openhuman/features/integrations/auto-fetch)中。无需提示词,无需手动编写轮询循环,智能体在每天早上就已经拥有当天的上下文。 + 托管集成使用 OpenHuman 的 Composio 连接器层。OAuth 握手和集成工具调用默认通过托管后端代理。如果你想直接运行 Composio,请使用你自己的 Composio API key 配置直连模式;实时触发器 webhook 则需要由你自行托管和接入。 + - **[记忆树](https://tinyhumans.gitbook.io/openhuman/features/memory-tree) + [Obsidian Wiki](https://tinyhumans.gitbook.io/openhuman/features/obsidian-wiki)**:一个基于你的数据和活动构建的本地优先知识库。你连接的所有内容都被规范化为不超过 3k token 的 Markdown 片段,经过评分后折叠成层级化的摘要树,存储在**你本机的 SQLite** 中。同样的片段以 `.md` 文件形式落地到兼容 Obsidian 的仓库中,你可以打开、浏览和编辑,灵感来源于 Karpathy 的 [obsidian-wiki 工作流](https://x.com/karpathy/status/2039805659525644595)。 -- **开箱即用**:默认内置网络搜索、网页抓取[爬虫](https://tinyhumans.gitbook.io/openhuman/features/native-tools)、完整的编码工具集(文件系统、git、lint、test、grep)以及[原生语音](https://tinyhumans.gitbook.io/openhuman/features/voice)(STT 输入、ElevenLabs TTS 输出、吉祥物口型同步、实时 Google Meet 智能体)。[模型路由](https://tinyhumans.gitbook.io/openhuman/features/model-routing)在一个订阅下将每个任务分派到合适的 LLM(推理型、快速型或视觉型)。没有"安装插件才能读文件"的摩擦。[可选通过 Ollama 使用本地 AI](https://tinyhumans.gitbook.io/openhuman/features/model-routing/local-ai) 处理端侧工作负载。 +- **开箱即用**:默认内置网络搜索、网页抓取[爬虫](https://tinyhumans.gitbook.io/openhuman/features/native-tools)、完整的编码工具集(文件系统、git、lint、test、grep)以及[原生语音](https://tinyhumans.gitbook.io/openhuman/features/voice)(STT 输入、ElevenLabs TTS 输出、吉祥物口型同步、实时 Google Meet 智能体)。默认情况下,[模型路由](https://tinyhumans.gitbook.io/openhuman/features/model-routing)使用 OpenHuman 后端来选择和代理每个工作负载的合适 LLM(推理型、快速型或视觉型)。一个订阅包含所有模型。没有"安装插件才能读文件"的摩擦。[可选通过 Ollama 使用本地 AI](https://tinyhumans.gitbook.io/openhuman/features/model-routing/local-ai) 处理端侧工作负载。 - **[智能 Token 压缩(TokenJuice)](https://tinyhumans.gitbook.io/openhuman/features/token-compression)**:每个工具调用、抓取结果、邮件正文和搜索载荷在触达任何 LLM 模型之前都会经过 token 压缩层处理。HTML 被转换为 Markdown,长 URL 被缩短,冗长的工具输出会通过可配置的规则层去重并摘要等等。中文、emoji 等多字节字符按字形(grapheme)完整保留,绝不丢弃。你获得相同的信息,但 token 消耗仅为原来的几分之一。最多可降低 80% 的成本和延迟。 diff --git a/app/.prettierignore b/app/.prettierignore index 8e4bde1b6b..cded8a49f4 100644 --- a/app/.prettierignore +++ b/app/.prettierignore @@ -3,6 +3,7 @@ dist coverage app src-tauri +src-tauri-mobile rust-core skills *.config.js diff --git a/app/eslint.config.js b/app/eslint.config.js index f4b626d2ad..b7b1380394 100644 --- a/app/eslint.config.js +++ b/app/eslint.config.js @@ -25,6 +25,7 @@ export default [ 'target/**', '**/target/**', 'dist/**', + 'dist-web/**', 'coverage/**', 'app/**', 'src-tauri/**', @@ -253,9 +254,9 @@ export default [ }, }, - // E2E test files (Appium/WebDriverIO) — use tsconfig.e2e.json for parsing + // E2E test files (WDIO + Playwright) — use tsconfig.e2e.json for parsing { - files: ['test/e2e/**/*.ts', 'test/wdio.conf.ts'], + files: ['test/e2e/**/*.ts', 'test/playwright/**/*.ts', 'test/wdio.conf.ts'], languageOptions: { parser: tsparser, parserOptions: { @@ -291,6 +292,18 @@ export default [ }, }, + // Playwright test helpers/specs are intentionally more permissive: + // empty catch blocks are used for best-effort browser-lane fallbacks and + // many helpers keep optional args/imports for parity with the WDIO suite. + { + files: ['test/playwright/**/*.ts'], + rules: { + 'no-empty': 'off', + 'no-unused-vars': 'off', + '@typescript-eslint/no-unused-vars': 'off', + }, + }, + // JavaScript files configuration { files: ['**/*.js', '**/*.jsx'], diff --git a/app/package.json b/app/package.json index 186ff41ba6..ef320ad872 100644 --- a/app/package.json +++ b/app/package.json @@ -1,6 +1,6 @@ { "name": "openhuman-app", - "version": "0.54.7", + "version": "0.57.1", "type": "module", "engines": { "node": ">=24.0.0" @@ -14,9 +14,16 @@ "dev:wry": "pnpm tauri:ensure && export CEF_PATH=\"$HOME/Library/Caches/tauri-cef\" && source ../scripts/load-dotenv.sh && cargo tauri dev --no-default-features --features wry", "core:stage": "echo '[core:stage] no-op — core is linked in-process; sidecar removed (PR #1061)'", "tauri:ensure": "bash ../scripts/ensure-tauri-cli.sh", + "tauri:ios:init": "bash ../scripts/ios-init.sh", + "tauri:ios:dev": "cd src-tauri-mobile && IPHONEOS_DEPLOYMENT_TARGET=${IPHONEOS_DEPLOYMENT_TARGET:-16.0} npx --package=@tauri-apps/cli@^2 tauri ios dev", + "tauri:ios:build": "cd src-tauri-mobile && IPHONEOS_DEPLOYMENT_TARGET=${IPHONEOS_DEPLOYMENT_TARGET:-16.0} npx --package=@tauri-apps/cli@^2 tauri ios build", + "tauri:android:init": "bash ../scripts/android-init.sh", + "tauri:android:dev": "cd src-tauri-mobile && npx --package=@tauri-apps/cli@^2 tauri android dev", + "tauri:android:build": "cd src-tauri-mobile && npx --package=@tauri-apps/cli@^2 tauri android build", "build": "tsc && vite build", "build:app": "tsc && vite build", "build:app:e2e": "tsc && vite build --mode development", + "build:web:e2e": "bash ./scripts/e2e-web-build.sh", "build:web": "cross-env VITE_OPENHUMAN_TARGET=web tsc && cross-env VITE_OPENHUMAN_TARGET=web vite build", "compile": "tsc --noEmit", "preview": "vite preview", @@ -37,14 +44,17 @@ "test:coverage": "vitest run --config test/vitest.config.ts --coverage", "test:rust": "bash ../scripts/test-rust-with-mock.sh", "test:e2e:build": "bash ./scripts/e2e-build.sh", + "test:e2e:web:build": "bash ./scripts/e2e-web-build.sh", + "test:e2e:web": "pnpm test:e2e:web:build && bash ./scripts/e2e-web-session.sh", + "test:e2e:mega": "pnpm test:e2e:build && bash ./scripts/e2e-run-spec.sh test/e2e/specs/mega-flow.spec.ts mega-flow", "test:e2e:login": "bash ./scripts/e2e-login.sh", "test:e2e:auth": "bash ./scripts/e2e-auth.sh", "test:e2e:service-connectivity": "OPENHUMAN_SERVICE_MOCK=1 bash ./scripts/e2e-run-spec.sh test/e2e/specs/service-connectivity-flow.spec.ts service-connectivity", "test:e2e:skills-registry": "bash ./scripts/e2e-run-spec.sh test/e2e/specs/skills-registry.spec.ts skills-registry", "test:e2e:cron-jobs": "bash ./scripts/e2e-run-spec.sh test/e2e/specs/cron-jobs-flow.spec.ts cron-jobs", - "test:e2e": "pnpm test:e2e:build && pnpm test:e2e:login && pnpm test:e2e:auth", + "test:e2e": "pnpm test:e2e:web && pnpm test:e2e:mega", "test:e2e:all:flows": "bash ./scripts/e2e-run-all-flows.sh", - "test:e2e:all": "pnpm test:e2e:build && pnpm test:e2e:all:flows", + "test:e2e:all": "pnpm test:e2e:web && pnpm test:e2e:all:flows", "test:e2e:session": "bash ./scripts/e2e-run-session.sh", "test:e2e:session:full": "pnpm test:e2e:build && pnpm test:e2e:session", "test:all": "pnpm test:coverage && pnpm test:rust && pnpm test:e2e", @@ -61,6 +71,7 @@ "knip:production": "knip --config knip.json --production" }, "dependencies": { + "@noble/ciphers": "^1.2.1", "@noble/curves": "^2.2.0", "@noble/hashes": "^2.0.1", "@noble/secp256k1": "^3.0.0", @@ -68,11 +79,13 @@ "@reduxjs/toolkit": "^2.11.2", "@remotion/player": "4.0.454", "@remotion/zod-types": "4.0.454", + "@rive-app/react-webgl2": "^4.28.6", "@scure/base": "^2.2.0", "@scure/bip32": "^2.0.1", "@scure/bip39": "^2.0.1", "@sentry/react": "^10.38.0", "@tauri-apps/api": "^2.10.0", + "@tauri-apps/plugin-barcode-scanner": "^2.4.4", "@tauri-apps/plugin-deep-link": "^2", "@tauri-apps/plugin-opener": "^2", "@tauri-apps/plugin-os": "^2.3.2", @@ -80,9 +93,11 @@ "buffer": "^6.0.3", "cmdk": "^1.1.1", "debug": "^4.4.3", + "katex": "^0.16.47", "lottie-react": "^2.4.1", "os-browserify": "^0.3.0", "process": "^0.11.10", + "qrcode.react": "^4.2.0", "react": "^19.1.0", "react-dom": "^19.1.0", "react-ga4": "^3.0.1", @@ -91,16 +106,20 @@ "react-markdown": "^10.1.0", "react-redux": "^9.2.0", "react-router-dom": "^7.13.0", - "redux-logger": "^3.0.6", + "recharts": "^2.15.0", "redux-persist": "^6.0.0", + "rehype-katex": "^7.0.1", + "remark-math": "^6.0.0", "remotion": "4.0.454", "socket.io-client": "^4.8.3", + "tauri-plugin-ptt-api": "workspace:*", "three": "^0.183.2", "util": "^0.12.5", "zod": "4.3.6" }, "devDependencies": { "@eslint/js": "^9.39.2", + "@playwright/test": "^1.56.1", "@sentry/vite-plugin": "^2.22.6", "@tailwindcss/forms": "^0.5.11", "@tailwindcss/typography": "^0.5.19", @@ -136,6 +155,7 @@ "knip": "^6.3.1", "postcss": "^8.5.6", "prettier": "^3.8.1", + "redux-logger": "^3.0.6", "tailwindcss": "^3.4.19", "typescript": "~5.8.3", "vite": "^8.0.0", diff --git a/app/playwright.config.ts b/app/playwright.config.ts new file mode 100644 index 0000000000..ef92b83116 --- /dev/null +++ b/app/playwright.config.ts @@ -0,0 +1,20 @@ +import { defineConfig } from '@playwright/test'; + +const baseURL = process.env.PW_BASE_URL || 'http://127.0.0.1:4173'; + +export default defineConfig({ + testDir: './test/playwright/specs', + fullyParallel: false, + workers: 1, + timeout: 60_000, + expect: { + timeout: 10_000, + }, + use: { + baseURL, + trace: 'retain-on-failure', + screenshot: 'only-on-failure', + video: 'retain-on-failure', + }, + reporter: [['list']], +}); diff --git a/app/public/tiny_mascot.riv b/app/public/tiny_mascot.riv new file mode 100644 index 0000000000..5e259e66fc Binary files /dev/null and b/app/public/tiny_mascot.riv differ diff --git a/app/scripts/e2e-build.sh b/app/scripts/e2e-build.sh index 2dd08b84bd..c51fabb32d 100755 --- a/app/scripts/e2e-build.sh +++ b/app/scripts/e2e-build.sh @@ -69,6 +69,13 @@ case "${CI:-}" in 1) export CI=true ;; 0) export CI=false ;; esac # All other build scripts in app/package.json do `pnpm tauri:ensure` + use # `cargo tauri build`; the E2E build was the one outlier and we got the panic. pnpm tauri:ensure +# ensure-tauri-cli.sh installs cargo-tauri into $INSTALL_ROOT/bin (default +#