* fix(coding-agent): 从用户 shell 取真实 PATH,打包后也找得到 CLI macOS 从 Finder 启动的 GUI 进程拿到的是一份极简 PATH,不经过用户的 shell 配置。 后果是:凡是版本管理器装的 agent CLI 在打包版里一个都找不到——`opencode` 官方脚本 装在 `~/.opencode/bin`,全局 npm 前缀和 nvm 的路径更是带版本号、根本没法硬编码。 用户只能手动去高级设置里填绝对路径。 改成先问用户的 shell 要一份真实 PATH。三个细节是踩出来的,都写进注释了: - 用 `-lic` 而不是 `-lc`。nvm 这类版本管理器通常在 .zshrc 里初始化,而 .zshrc 只有 交互式 shell 才读;`-lc` 拿到的 PATH 里 nvm 排在 /usr/local/bin 之后, `#!/usr/bin/env node` 会命中错版本的 node,子进程直接崩。 - 拼接必须保序。逐项前插等于把整个 PATH 倒过来,同样会选错解释器。 - 分隔符交给 std::env::split_paths / join_paths,不能手写 `:`。Windows 用 `;`, 而且盘符自带冒号,手写会把 `C:\Windows` 切成两半、整条 PATH 报废。 shell 卡住有 5 秒硬上限,超时回落到静态目录列表。整段探测限定 unix:Windows 没有 「登录 shell 的 PATH」这个概念,装了 Git Bash 的机器 SHELL 有值,真去跑只会莫名其妙 拉起一个 bash。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * feat(codex): 无头 Codex 适配器 Less Computer 现在只认 Claude Code 和 OpenCode,因为每个后端都得手写一套适配器。 这个是 Codex 的那套,形状和前两家一致,共用同一套请求/事件/错误类型。 - 运行:`codex exec --json`,逐行解析 JSONL。 - prompt 走 stdin(末尾的 `-`),不进 argv,也不会出现在进程列表里。 - 续接用 `resume --last`。参数顺序有坑:`-s` / `--json` 这些必须排在 `resume` 之前, 写反了 CLI 直接报 unexpected argument,已对 0.146.0 核实并写进注释。 - 工具展示会剥掉 `/bin/zsh -lc '…'` 这层 shell 包装,胶囊里显示的是真正执行的命令名。 - 护栏:Codex 给不了逐命令 deny 清单(`.rules` 只从 $CODEX_HOME / 项目目录读,实测 `-c rules=[…]` 会被静默忽略)。它的护栏是自带的 seatbelt 沙箱,走 `-s <mode>`。 额外关掉 workspace-write 对 /tmp 与 $TMPDIR 的默认放行——不关的话「写入限制在工作 目录内」就是句假话,实测任务真的能在 $TMPDIR 里落文件。 有一处刻意不做:Codex 会把「模型元数据没找到」这类警告也塞成 error 事件,本机每次运行 都会发一条。把它当终局失败会让所有运行都被误判,所以只认 turn.failed。 联机验收默认 #[ignore],其中一条不调模型不花钱:它比对加固前后实际生效的可写根, 盯住护栏别被上游无声改宽(codex 对未知 -c 键是静默接受的,改名了不会报错)。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * feat(coding-agent): Less Computer 可选 Codex 后端,审批卡按能力启用 把 Codex 接进语音链路,后端从两家变三家。 护栏这块两类后端不一样,代码里如实分开:Claude / OpenCode 有逐命令 deny 清单,撞了能 弹审批卡、批准后放行那一条重跑;Codex 只有粗粒度沙箱档位,唯一的「放行」办法是整体 降档,那等于把护栏关掉,不是放行这一条。 所以给 provider 加了一个能力位,只有支持逐命令放行的后端才弹审批卡。否则用户点了批准、 重跑还是同样被拦,那是个假承诺。撞墙时直接把失败如实报出去。 模型解析也按后端分开:Codex 收裸模型名(gpt-5 / o3 这种),留空交给它自己的 ~/.codex/config.toml,不替用户瞎猜一个默认。 另加一个通用的安装检测命令:选了后端却没装 CLI 时,在设置页当场看到,而不是等说完 一句话才发现跑不起来。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * feat(settings): Less Computer 后端下拉加入 Codex 设置页的后端下拉从两项变三项,并且如实反映能力差异,不给假开关: - 模型:Claude 用别名下拉,OpenCode 拉账号可用列表,Codex 改成自由文本——它的模型名 是裸名(gpt-5 / 自建网关的任意名字),枚举不过来;留空 = 用 Codex 自己的默认。 - 护栏:选了 Codex 会挂一条说明,讲清楚它只有粗粒度沙箱、撞到限制会直接报错而不是弹 「批准这条命令」的卡片。免得用户按 Claude 的心智去用。 - 装没装:当场检测并提示,没装时按警示色显示。 - 可执行文件的 placeholder 跟着后端走。 文案 5 语言齐全。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(coding-agent): harden Codex execution --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: Chris233 <h-chris233@outlook.com>
331 lines
13 KiB
YAML
331 lines
13 KiB
YAML
name: CI
|
||
|
||
# 多平台跨语言质量门禁。release-tauri.yml 是发版流水线(仅打包构建),这里负责
|
||
# 在合并前快速验证两件事:
|
||
# 1. 全部前端/契约测试与 vite bundle 通过(捕获行为、合同及跨 locale 类型 drift)
|
||
# 2. Rust 后端在 macOS / Windows / Linux 都能 cargo check 过(捕获 cfg 漏分支 / 平台 API 误用)
|
||
# 跑 build-mac.sh / windows-package-msvc.ps1 / Tauri bundle 太重;只跑轻量 cargo check + vite build。
|
||
|
||
on:
|
||
push:
|
||
branches: [main, beta]
|
||
pull_request:
|
||
branches: [main, beta]
|
||
workflow_dispatch:
|
||
|
||
# 同一 PR 快速重复推送时取消旧运行;workflow_dispatch 用 run_id 隔离。
|
||
concurrency:
|
||
group: ${{ github.workflow }}-${{ github.event_name == 'workflow_dispatch' && github.run_id || github.ref }}
|
||
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||
|
||
jobs:
|
||
android-check:
|
||
name: Android cargo check
|
||
runs-on: ubuntu-latest
|
||
defaults:
|
||
run:
|
||
working-directory: openless-all/app
|
||
steps:
|
||
- uses: actions/checkout@v4
|
||
with:
|
||
# Android 不使用任何桌面本地 ASR 子模块;macOS MLX 依赖不进入此任务。
|
||
submodules: false
|
||
|
||
# Android 不编译任何本地 ASR C 代码:build.rs 按 TARGET 解析排除
|
||
# Android triple(不调 build_qwen_asr)。qwen3-asr-rs 是 path 依赖,
|
||
# Cargo 解析器跨所有 target 都要读它的 manifest——所以由下方的
|
||
# ci-disable-macos-qwen3.mjs 删掉该依赖行,否则 cargo check 硬失败。
|
||
# 去掉递归拉取省一次网络 fetch + 失败点。
|
||
- name: Setup Android SDK + NDK
|
||
uses: android-actions/setup-android@v3
|
||
with:
|
||
packages: platform-tools
|
||
|
||
- name: Install NDK
|
||
shell: bash
|
||
run: |
|
||
set -euo pipefail
|
||
sdkmanager "ndk;26.1.10909125" "platforms;android-36" "build-tools;36.0.0"
|
||
ndk_dir="$ANDROID_HOME/ndk/26.1.10909125"
|
||
echo "ANDROID_NDK_HOME=$ndk_dir" >> "$GITHUB_ENV"
|
||
echo "NDK_HOME=$ndk_dir" >> "$GITHUB_ENV"
|
||
set +o pipefail
|
||
yes | sdkmanager --licenses
|
||
set -o pipefail
|
||
|
||
- name: Configure NDK toolchain for cargo
|
||
shell: bash
|
||
run: |
|
||
set -euo pipefail
|
||
prebuilt="$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64"
|
||
echo "CC_aarch64_linux_android=$prebuilt/bin/aarch64-linux-android24-clang" >> "$GITHUB_ENV"
|
||
echo "CXX_aarch64_linux_android=$prebuilt/bin/aarch64-linux-android24-clang++" >> "$GITHUB_ENV"
|
||
echo "AR_aarch64_linux_android=$prebuilt/bin/llvm-ar" >> "$GITHUB_ENV"
|
||
echo "CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER=$prebuilt/bin/aarch64-linux-android24-clang" >> "$GITHUB_ENV"
|
||
echo "CC_x86_64_linux_android=$prebuilt/bin/x86_64-linux-android24-clang" >> "$GITHUB_ENV"
|
||
echo "CXX_x86_64_linux_android=$prebuilt/bin/x86_64-linux-android24-clang++" >> "$GITHUB_ENV"
|
||
echo "AR_x86_64_linux_android=$prebuilt/bin/llvm-ar" >> "$GITHUB_ENV"
|
||
echo "CARGO_TARGET_X86_64_LINUX_ANDROID_LINKER=$prebuilt/bin/x86_64-linux-android24-clang" >> "$GITHUB_ENV"
|
||
|
||
- uses: actions/setup-node@v4
|
||
with:
|
||
node-version: "22"
|
||
cache: npm
|
||
cache-dependency-path: openless-all/app/package-lock.json
|
||
|
||
- uses: actions/setup-java@v4
|
||
with:
|
||
distribution: zulu
|
||
java-version: "17"
|
||
|
||
- uses: dtolnay/rust-toolchain@stable
|
||
with:
|
||
targets: aarch64-linux-android,x86_64-linux-android
|
||
|
||
- uses: swatinem/rust-cache@v2
|
||
with:
|
||
workspaces: 'openless-all/app/src-tauri -> target'
|
||
|
||
- uses: gradle/actions/setup-gradle@v4
|
||
|
||
- name: Install Linux check dependencies
|
||
run: |
|
||
sudo apt-get update
|
||
sudo apt-get install -y build-essential curl file libssl-dev wget
|
||
|
||
- name: Install frontend dependencies
|
||
run: npm ci
|
||
|
||
- name: Build frontend (tsc + vite)
|
||
# generate_context! requires frontendDist (../dist) to exist on Android too.
|
||
run: npm run build
|
||
|
||
- name: Check Android updater pubkey matches tauri.conf.json
|
||
run: npm run check:android-updater-pubkey
|
||
|
||
- name: Disable macOS-only Qwen3 MLX dependency
|
||
run: node scripts/ci-disable-macos-qwen3.mjs
|
||
|
||
- name: Check Tauri backend (Android target)
|
||
run: cargo check --manifest-path src-tauri/Cargo.toml --target aarch64-linux-android
|
||
|
||
- name: Initialize generated Android project
|
||
run: npm run tauri -- android init --ci
|
||
|
||
- name: Generate Tauri Gradle dependency scripts
|
||
env:
|
||
TAURI_ANDROID_PROJECT_PATH: ${{ github.workspace }}/openless-all/app/src-tauri/gen/android
|
||
TAURI_ANDROID_PACKAGE_UNESCAPED: com.openless.app
|
||
WRY_ANDROID_LIBRARY: openless_lib
|
||
WRY_ANDROID_KOTLIN_FILES_OUT_DIR: ${{ github.workspace }}/openless-all/app/src-tauri/gen/android/app/src/main/java/com/openless/app/generated
|
||
WRY_ANDROID_PACKAGE: com.openless.app
|
||
run: |
|
||
mkdir -p "$WRY_ANDROID_KOTLIN_FILES_OUT_DIR"
|
||
cargo check --manifest-path src-tauri/Cargo.toml --target x86_64-linux-android
|
||
|
||
- name: Copy Android production and test scaffolding
|
||
run: node scripts/copy-android-scaffolding.mjs
|
||
|
||
- name: Merge Shizuku manifest
|
||
run: node scripts/merge-android-shizuku-manifest.mjs
|
||
|
||
- name: Patch Shizuku Gradle dependencies
|
||
run: node scripts/patch-android-shizuku-deps.mjs
|
||
|
||
- name: Run JVM credential tests and compile instrumentation tests
|
||
# These tests are Kotlin-only. The Rust target is checked above; direct Gradle
|
||
# rustBuild requires the live Tauri CLI RPC server used by `tauri android build`.
|
||
run: >-
|
||
src-tauri/gen/android/gradlew
|
||
--project-dir src-tauri/gen/android
|
||
:app:testX86_64DebugUnitTest
|
||
:app:assembleX86_64DebugAndroidTest
|
||
-x :app:rustBuildX86_64Debug
|
||
--no-daemon
|
||
|
||
- name: Enable KVM for Android instrumentation tests
|
||
run: |
|
||
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' \
|
||
| sudo tee /etc/udev/rules.d/99-kvm4all.rules
|
||
sudo udevadm control --reload-rules
|
||
sudo udevadm trigger --name-match=kvm
|
||
|
||
- name: Run Android Keystore instrumentation tests
|
||
uses: reactivecircus/android-emulator-runner@v2
|
||
with:
|
||
api-level: 35
|
||
target: google_apis
|
||
arch: x86_64
|
||
# The instrumentation suite exercises AndroidKeyStore without launching Tauri.
|
||
script: >-
|
||
cd openless-all/app &&
|
||
src-tauri/gen/android/gradlew
|
||
--project-dir src-tauri/gen/android
|
||
:app:connectedX86_64DebugAndroidTest
|
||
-x :app:rustBuildX86_64Debug
|
||
--no-daemon
|
||
|
||
cross-platform:
|
||
name: ${{ matrix.label }} checks
|
||
strategy:
|
||
# 一个平台挂掉不阻塞其他平台拿到验证结果。
|
||
fail-fast: false
|
||
matrix:
|
||
include:
|
||
- os: macos-latest
|
||
label: macOS
|
||
preflight: false
|
||
- os: windows-latest
|
||
label: Windows
|
||
preflight: true
|
||
- os: ubuntu-latest
|
||
label: Linux
|
||
preflight: false
|
||
runs-on: ${{ matrix.os }}
|
||
defaults:
|
||
run:
|
||
working-directory: openless-all/app
|
||
steps:
|
||
- uses: actions/checkout@v4
|
||
with:
|
||
# 只在 macOS 初始化 MLX 子模块;Linux 的 C 后端单独初始化 qwen-asr。
|
||
submodules: ${{ matrix.os == 'macos-latest' && 'recursive' || 'false' }}
|
||
|
||
- name: Initialize Linux C ASR submodule
|
||
if: runner.os == 'Linux'
|
||
shell: bash
|
||
run: git submodule update --init src-tauri/vendor/qwen-asr
|
||
- name: Disable macOS-only Qwen3 MLX dependency
|
||
if: runner.os != 'macOS'
|
||
run: node scripts/ci-disable-macos-qwen3.mjs
|
||
|
||
- uses: actions/setup-node@v4
|
||
with:
|
||
node-version: "22"
|
||
cache: npm
|
||
cache-dependency-path: openless-all/app/package-lock.json
|
||
|
||
# 现有测试继续用 stable;额外安装声明的 MSRV,供下方兼容性门禁显式调用。
|
||
- uses: dtolnay/rust-toolchain@1.88.0
|
||
|
||
- uses: dtolnay/rust-toolchain@stable
|
||
|
||
- uses: swatinem/rust-cache@v2
|
||
with:
|
||
workspaces: 'openless-all/app/src-tauri -> target'
|
||
|
||
- name: Install Linux check dependencies
|
||
if: runner.os == 'Linux'
|
||
run: |
|
||
sudo apt-get update
|
||
sudo apt-get install -y \
|
||
build-essential \
|
||
curl \
|
||
file \
|
||
libasound2-dev \
|
||
libayatana-appindicator3-dev \
|
||
librsvg2-dev \
|
||
libssl-dev \
|
||
libwebkit2gtk-4.1-dev \
|
||
libxdo-dev \
|
||
wget
|
||
|
||
- name: Install frontend dependencies
|
||
run: npm ci
|
||
|
||
- name: Check Windows prerequisites
|
||
if: matrix.preflight
|
||
shell: pwsh
|
||
run: ./scripts/windows-preflight.ps1 -Toolchain msvc
|
||
|
||
- name: Check PowerShell scripts
|
||
if: matrix.preflight
|
||
shell: pwsh
|
||
run: |
|
||
foreach ($script in @("./scripts/windows-preflight.ps1", "./scripts/windows-build-gnu.ps1", "./scripts/windows-runtime-smoke.ps1")) {
|
||
$errors = $null
|
||
[System.Management.Automation.PSParser]::Tokenize((Get-Content -Raw $script), [ref]$errors) | Out-Null
|
||
if ($errors) {
|
||
$errors | Format-List
|
||
exit 1
|
||
}
|
||
}
|
||
|
||
- name: Build frontend and run frontend/contract tests
|
||
run: npm test
|
||
|
||
- name: Install pinned Codex CLI for sandbox contract
|
||
if: runner.os == 'macOS'
|
||
run: npm install --global @openai/codex@0.146.0
|
||
|
||
- name: Configure isolated Codex home
|
||
if: runner.os == 'macOS'
|
||
shell: bash
|
||
run: |
|
||
set -euo pipefail
|
||
codex_home="$RUNNER_TEMP/openless-codex-home"
|
||
mkdir -p "$codex_home"
|
||
echo "CODEX_HOME=$codex_home" >> "$GITHUB_ENV"
|
||
|
||
- name: Run Codex writable-roots sandbox contract
|
||
if: runner.os == 'macOS'
|
||
env:
|
||
OPENLESS_CODEX_TEST_EXE: codex
|
||
run: cargo test --manifest-path src-tauri/Cargo.toml --lib coding_agent::codex::live::hardening_actually_narrows_the_writable_roots -- --ignored --nocapture --test-threads=1
|
||
|
||
- name: Check Tauri backend (cargo check)
|
||
run: cargo check --manifest-path src-tauri/Cargo.toml
|
||
|
||
- name: Run Rust backend unit tests
|
||
if: runner.os != 'Windows'
|
||
run: cargo test --manifest-path src-tauri/Cargo.toml --lib
|
||
|
||
- name: Compile Rust backend unit tests (Windows)
|
||
# Windows runner 能链接 lib test binary,但干净镜像缺少可选 native runtime
|
||
# DLL entrypoint 时,进程会在 test harness 启动前退出。这里保留 cfg/link
|
||
# 覆盖;Rust-only 后端单测由下一步实际执行。
|
||
if: runner.os == 'Windows'
|
||
run: cargo test --manifest-path src-tauri/Cargo.toml --lib --no-run
|
||
|
||
- name: Run Rust-only backend unit tests (Windows)
|
||
# 只把纯 Rust 后端模块编进独立 test crate,不链接完整 Tauri app lib。
|
||
# 这样 Windows CI 能实际执行 coordinator/hotkey/recorder/insertion 逻辑单测。
|
||
if: runner.os == 'Windows'
|
||
run: cargo test --manifest-path src-tauri/backend-tests/Cargo.toml
|
||
|
||
- name: Check Tauri backend with Rust 1.88 MSRV
|
||
run: cargo +1.88.0 check --locked --manifest-path src-tauri/Cargo.toml
|
||
|
||
- name: Compile backend tests with Rust 1.88 MSRV
|
||
run: cargo +1.88.0 test --locked --manifest-path src-tauri/backend-tests/Cargo.toml --no-run
|
||
|
||
- name: Verify version sync across all 5 files
|
||
# 两个平台都跑这个校验:Windows runner 自带 git-bash,跨 shell 表现一致。
|
||
# 一旦版本号 drift 立刻 fail,避免发版时再发现漏改。
|
||
# 校验 5 处:package.json / package-lock.json (root + nested) /
|
||
# tauri.conf.json / Cargo.toml / Cargo.lock 的 [openless] 包。
|
||
shell: bash
|
||
run: |
|
||
PKG=$(node -p "require('./package.json').version")
|
||
LOCK_ROOT=$(node -p "require('./package-lock.json').version")
|
||
LOCK_NESTED=$(node -p "require('./package-lock.json').packages[''].version")
|
||
TAU=$(node -p "require('./src-tauri/tauri.conf.json').version")
|
||
CRG=$(grep -E '^version = ' src-tauri/Cargo.toml | head -1 | sed -E 's/^version = "(.+)"$/\1/')
|
||
# Cargo.lock:找 [openless] 包紧跟的 version 行
|
||
CARGO_LOCK_VER=$(awk 'BEGIN{found=0} /^name = "openless"$/{found=1; next} found && /^version = /{gsub(/"/,""); print $3; exit}' src-tauri/Cargo.lock)
|
||
echo "package.json = $PKG"
|
||
echo "package-lock root = $LOCK_ROOT"
|
||
echo "package-lock nested = $LOCK_NESTED"
|
||
echo "tauri.conf.json = $TAU"
|
||
echo "Cargo.toml = $CRG"
|
||
echo "Cargo.lock openless = $CARGO_LOCK_VER"
|
||
mismatch=0
|
||
for v in "$LOCK_ROOT" "$LOCK_NESTED" "$TAU" "$CRG" "$CARGO_LOCK_VER"; do
|
||
if [ "$v" != "$PKG" ]; then mismatch=1; fi
|
||
done
|
||
if [ "$mismatch" -ne 0 ]; then
|
||
echo "::error::版本号未对齐 — 请用 scripts/bump-version.sh 同步更新"
|
||
exit 1
|
||
fi
|
||
echo "[ok] 全部 5 处版本号一致:$PKG"
|