Skip to content

feat(sight): restructure config to https/http rules and fix BoringSSL + FFI delivery #1347

feat(sight): restructure config to https/http rules and fix BoringSSL + FFI delivery

feat(sight): restructure config to https/http rules and fix BoringSSL + FFI delivery #1347

Workflow file for this run

name: CI
on:
push:
branches: [main, 'release/**']
pull_request:
branches: [main, 'release/**']
workflow_dispatch:
inputs:
run_copilot_shell:
description: 'Force run copilot-shell tests (ignore change detection)'
required: false
type: boolean
default: false
run_agent_sec:
description: 'Force run agent-sec-core tests (ignore change detection)'
required: false
type: boolean
default: false
run_agentsight:
description: 'Force run agentsight tests (ignore change detection)'
required: false
type: boolean
default: false
run_tokenless:
description: 'Force run tokenless tests (ignore change detection)'
required: false
type: boolean
default: false
run_ws_ckpt:
description: 'Force run ws-ckpt tests (ignore change detection)'
required: false
type: boolean
default: false
permissions:
contents: read
jobs:
# =========================================================================
# Step 1: Detect which components have changed
# =========================================================================
detect-changes:
name: Detect Changes
runs-on: ubuntu-22.04
outputs:
copilot_shell: ${{ steps.changes.outputs.copilot_shell }}
agent_sec_core: ${{ steps.changes.outputs.agent_sec_core }}
agentsight: ${{ steps.changes.outputs.agentsight }}
tokenless: ${{ steps.changes.outputs.tokenless }}
ws_ckpt: ${{ steps.changes.outputs.ws_ckpt }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 2
- name: Check which components changed
id: changes
run: |
set -e
echo "=== Detect Changes ==="
# Get changed files
if git rev-parse HEAD~1 >/dev/null 2>&1; then
CHANGED=$(git diff --name-only HEAD~1 HEAD || true)
else
CHANGED=$(git diff --name-only --diff-filter=ACMRT HEAD || true)
fi
echo "Changed files:"
echo "$CHANGED"
COPILOT_SHELL=false
AGENT_SEC=false
AGENTSIGHT=false
TOKENLESS=false
WS_CKPT=false
# Path-based detection
if echo "$CHANGED" | grep -q "^src/copilot-shell/"; then
COPILOT_SHELL=true
fi
if echo "$CHANGED" | grep -q "^src/agent-sec-core/"; then
AGENT_SEC=true
fi
if echo "$CHANGED" | grep -q "^src/agentsight/"; then
AGENTSIGHT=true
fi
if echo "$CHANGED" | grep -q "^src/tokenless/"; then
TOKENLESS=true
fi
if echo "$CHANGED" | grep -q "^src/ws-ckpt/"; then
WS_CKPT=true
fi
# Manual override via workflow_dispatch
if [[ "${{ inputs.run_copilot_shell }}" == "true" ]]; then
COPILOT_SHELL=true
fi
if [[ "${{ inputs.run_agent_sec }}" == "true" ]]; then
AGENT_SEC=true
fi
if [[ "${{ inputs.run_agentsight }}" == "true" ]]; then
AGENTSIGHT=true
fi
if [[ "${{ inputs.run_tokenless }}" == "true" ]]; then
TOKENLESS=true
fi
if [[ "${{ inputs.run_ws_ckpt }}" == "true" ]]; then
WS_CKPT=true
fi
echo "copilot_shell=$COPILOT_SHELL" >> $GITHUB_OUTPUT
echo "agent_sec_core=$AGENT_SEC" >> $GITHUB_OUTPUT
echo "agentsight=$AGENTSIGHT" >> $GITHUB_OUTPUT
echo "tokenless=$TOKENLESS" >> $GITHUB_OUTPUT
echo "ws_ckpt=$WS_CKPT" >> $GITHUB_OUTPUT
echo "### Change Detection Results" >> $GITHUB_STEP_SUMMARY
echo "| Component | Changed |" >> $GITHUB_STEP_SUMMARY
echo "|-----------|---------|" >> $GITHUB_STEP_SUMMARY
echo "| copilot-shell | $COPILOT_SHELL |" >> $GITHUB_STEP_SUMMARY
echo "| agent-sec-core | $AGENT_SEC |" >> $GITHUB_STEP_SUMMARY
echo "| agentsight | $AGENTSIGHT |" >> $GITHUB_STEP_SUMMARY
echo "| tokenless | $TOKENLESS |" >> $GITHUB_STEP_SUMMARY
echo "| ws-ckpt | $WS_CKPT |" >> $GITHUB_STEP_SUMMARY
# =========================================================================
# Step 2: Build & Lint copilot-shell
# =========================================================================
build-copilot-shell:
name: Build copilot-shell
needs: detect-changes
if: needs.detect-changes.outputs.copilot_shell == 'true'
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: src/copilot-shell/package-lock.json
- name: Install dependencies
run: cd src/copilot-shell && npm ci
- name: Check formatting
run: |
cd src/copilot-shell
npm run format
git diff --exit-code
- name: Lint
run: cd src/copilot-shell && npm run lint:ci
- name: Build
run: cd src/copilot-shell && npm run build
- name: Type check
run: cd src/copilot-shell && npm run typecheck
- name: Upload build artifacts
uses: actions/upload-artifact@v4
with:
name: build-dist-nodejs20
path: |
src/copilot-shell/packages/*/dist/**/*
src/copilot-shell/package-lock.json
retention-days: 1
# =========================================================================
# Step 3: Test copilot-shell (cli + core in parallel)
# =========================================================================
test-copilot-shell-cli:
name: Test copilot-shell/cli
needs: [detect-changes, build-copilot-shell]
if: needs.detect-changes.outputs.copilot_shell == 'true'
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: src/copilot-shell/package-lock.json
- uses: actions/download-artifact@v4
with:
name: build-dist-nodejs20
path: '.'
- name: Install and build
run: |
cd src/copilot-shell
npm ci
npm run build
- name: Run cli tests
run: |
cd src/copilot-shell
NO_COLOR=true npm run test:ci --workspace=@copilot-shell/cli
test-copilot-shell-core:
name: Test copilot-shell/core
needs: [detect-changes, build-copilot-shell]
if: needs.detect-changes.outputs.copilot_shell == 'true'
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: src/copilot-shell/package-lock.json
- uses: actions/download-artifact@v4
with:
name: build-dist-nodejs20
path: '.'
- name: Install and build
run: |
cd src/copilot-shell
npm ci
npm run build
- name: Run core tests
run: |
cd src/copilot-shell
NO_COLOR=true npm run test:ci --workspace=@copilot-shell/core
# =========================================================================
# Step 4: Test agent-sec-core (Linux only)
# =========================================================================
test-agent-sec-core:
name: Test agent-sec-core
needs: detect-changes
if: needs.detect-changes.outputs.agent_sec_core == 'true'
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: dtolnay/rust-toolchain@stable
with:
toolchain: '1.93.0'
components: llvm-tools-preview
- name: Install cargo-llvm-cov
uses: taiki-e/install-action@cargo-llvm-cov
- name: astral-sh/setup-uv
uses: astral-sh/setup-uv@v8.0.0
with:
python-version: '3.11.6'
- uses: Swatinem/rust-cache@v2
with:
workspaces: src/agent-sec-core/linux-sandbox
- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y clang llvm libssl-dev libseccomp-dev bubblewrap
- name: Install diff-cover
run: uv tool install diff-cover
- name: Check formatting
run: |
cd src/agent-sec-core
make python-code-pretty
if ! git diff-index --quiet HEAD --; then
echo "ERROR: Code style check failed. Please run 'make python-code-pretty' locally." >&2
git status --porcelain >&2
exit 1
fi
echo "Code style check passed."
- name: Lint check (incremental)
if: github.event_name == 'pull_request'
run: |
cd src/agent-sec-core
uv run --project agent-sec-cli ruff check --config agent-sec-cli/pyproject.toml --output-format=concise . > ruff_report.txt || true
# Prefix paths to match git-diff repo-root-relative paths
sed -i 's|^\([^: ]*\.py\)|src/agent-sec-core/\1|' ruff_report.txt
cd "$GITHUB_WORKSPACE"
LINT_OUTPUT=$(diff-quality --violations=flake8 --fail-under=100 src/agent-sec-core/ruff_report.txt 2>&1) || true
rm -f src/agent-sec-core/ruff_report.txt
echo "$LINT_OUTPUT"
# Extract violation count from diff-quality output
if echo "$LINT_OUTPUT" | grep -q "Failure"; then
{
echo "### ⚠️ agent-sec-core Lint Warnings (incremental)"
echo ""
echo '以下为 PR 变更行中的 ruff lint 违规(不卡点,仅提示):'
echo ""
echo '```'
echo "$LINT_OUTPUT"
echo '```'
} >> "$GITHUB_STEP_SUMMARY"
echo "::warning::Lint violations found in changed lines. See step summary for details."
else
echo "### ✅ agent-sec-core Lint Check Passed" >> "$GITHUB_STEP_SUMMARY"
fi
- name: Run Python tests with coverage
run: |
cd src/agent-sec-core
make test-python-coverage
- name: Generate coverage summary
if: always()
shell: bash
run: |
cd src/agent-sec-core
if [ -f coverage.xml ]; then
LINE_RATE=$(grep -oP 'line-rate="\K[^"]+' coverage.xml | head -1)
BRANCH_RATE=$(grep -oP 'branch-rate="\K[^"]+' coverage.xml | head -1)
LINES_VALID=$(grep -oP 'lines-valid="\K[^"]+' coverage.xml | head -1)
LINES_COVERED=$(grep -oP 'lines-covered="\K[^"]+' coverage.xml | head -1)
BRANCHES_VALID=$(grep -oP 'branches-valid="\K[^"]+' coverage.xml | head -1)
BRANCHES_COVERED=$(grep -oP 'branches-covered="\K[^"]+' coverage.xml | head -1)
LINE_PCT=$(echo "$LINE_RATE * 100" | bc -l | xargs printf '%.1f')
BRANCH_PCT=$(echo "$BRANCH_RATE * 100" | bc -l | xargs printf '%.1f')
{
echo "### 🧪 agent-sec-core (Python) Test Coverage"
echo ""
echo "| Metric | Coverage | Detail |"
echo "|--------|----------|--------|"
echo "| 🟢 Line Coverage | **${LINE_PCT}%** | ${LINES_COVERED}/${LINES_VALID} lines |"
echo "| 🟢 Branch Coverage | **${BRANCH_PCT}%** | ${BRANCHES_COVERED}/${BRANCHES_VALID} branches |"
} >> "$GITHUB_STEP_SUMMARY"
else
echo "### ⚠️ Coverage report not found" >> "$GITHUB_STEP_SUMMARY"
fi
- name: Check uv.lock in sync with pyproject.toml
run: |
cd src/agent-sec-core/agent-sec-cli
uv lock --check
- name: Check requirements.txt in sync with uv.lock
run: |
cd src/agent-sec-core
make export-requirements
if ! git diff --quiet -- agent-sec-cli/requirements.txt; then
echo "ERROR: agent-sec-cli/requirements.txt is out of sync with uv.lock." >&2
echo "Please run 'make export-requirements' locally and commit the result." >&2
git diff -- agent-sec-cli/requirements.txt >&2
exit 1
fi
echo "requirements.txt is in sync with uv.lock."
- name: Run Rust tests with coverage
run: |
cd src/agent-sec-core
make test-rust-coverage
- name: Generate Rust coverage summary
if: always()
shell: bash
run: |
COV_FILE="src/agent-sec-core/rust-coverage.xml"
if [ -f "$COV_FILE" ]; then
LINE_RATE=$(grep -oP 'line-rate="\K[^"]+' "$COV_FILE" | head -1)
LINES_VALID=$(grep -oP 'lines-valid="\K[^"]+' "$COV_FILE" | head -1)
LINES_COVERED=$(grep -oP 'lines-covered="\K[^"]+' "$COV_FILE" | head -1)
LINE_PCT=$(echo "$LINE_RATE * 100" | bc -l | xargs printf '%.1f')
{
echo "### 🧪 agent-sec-core/linux-sandbox (Rust) Test Coverage"
echo ""
echo "| Metric | Coverage | Detail |"
echo "|--------|----------|--------|"
echo "| 🟢 Line Coverage | **${LINE_PCT}%** | ${LINES_COVERED}/${LINES_VALID} lines |"
echo ""
echo "> ℹ️ cargo-llvm-cov 不支持分支覆盖统计,仅展示行覆盖率。"
} >> "$GITHUB_STEP_SUMMARY"
else
echo "### ⚠️ linux-sandbox Rust coverage report not found" >> "$GITHUB_STEP_SUMMARY"
fi
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Run openclaw-plugin tests with coverage
run: |
cd src/agent-sec-core
cd openclaw-plugin && npm install
cd ..
make test-openclaw-plugin-coverage
- name: Generate openclaw-plugin coverage summary
if: always()
shell: bash
run: |
COV_FILE="src/agent-sec-core/openclaw-plugin/coverage/cobertura-coverage.xml"
if [ -f "$COV_FILE" ]; then
LINE_RATE=$(grep -oP 'line-rate="\K[^"]+' "$COV_FILE" | head -1)
BRANCH_RATE=$(grep -oP 'branch-rate="\K[^"]+' "$COV_FILE" | head -1)
LINES_VALID=$(grep -oP 'lines-valid="\K[^"]+' "$COV_FILE" | head -1)
LINES_COVERED=$(grep -oP 'lines-covered="\K[^"]+' "$COV_FILE" | head -1)
BRANCHES_VALID=$(grep -oP 'branches-valid="\K[^"]+' "$COV_FILE" | head -1)
BRANCHES_COVERED=$(grep -oP 'branches-covered="\K[^"]+' "$COV_FILE" | head -1)
LINE_PCT=$(echo "$LINE_RATE * 100" | bc -l | xargs printf '%.1f')
BRANCH_PCT=$(echo "$BRANCH_RATE * 100" | bc -l | xargs printf '%.1f')
{
echo "### 🧪 agent-sec-core/openclaw-plugin (TypeScript) Test Coverage"
echo ""
echo "| Metric | Coverage | Detail |"
echo "|--------|----------|--------|"
echo "| 🟢 Line Coverage | **${LINE_PCT}%** | ${LINES_COVERED}/${LINES_VALID} lines |"
echo "| 🟢 Branch Coverage | **${BRANCH_PCT}%** | ${BRANCHES_COVERED}/${BRANCHES_VALID} branches |"
} >> "$GITHUB_STEP_SUMMARY"
else
echo "### ⚠️ openclaw-plugin coverage report not found" >> "$GITHUB_STEP_SUMMARY"
fi
- name: Incremental coverage gate (PR only)
if: github.event_name == 'pull_request'
shell: bash
run: |
cd src/agent-sec-core
COMPARE_BRANCH="origin/${{ github.base_ref }}"
echo "=== Diff-cover: comparing against $COMPARE_BRANCH ==="
DIFF_COV_SUMMARY=""
GATE_FAILED=false
THRESHOLD=80
# Helper: extract coverage stats from diff-cover output
extract_diff_cov_stats() {
local output="$1"
local pct missing total covered
pct=$(echo "$output" | grep -oP 'Coverage:\s*\K[0-9.]+(?=%)' || echo "")
total=$(echo "$output" | grep -oP 'Total:\s+\K[0-9]+' || echo "")
missing=$(echo "$output" | grep -oP 'Missing:\s*\K[0-9]+' || echo "")
if [ -n "$total" ] && [ -n "$missing" ]; then
covered=$((total - missing))
echo "${pct}|${covered}/${total} lines"
elif [ -n "$pct" ]; then
echo "${pct}|-"
else
echo "N/A|-"
fi
}
# --- Python ---
if [ -f coverage.xml ]; then
echo "--- Python incremental coverage ---"
PYTHON_OUTPUT=$(diff-cover coverage.xml --compare-branch="$COMPARE_BRANCH" --fail-under=$THRESHOLD 2>&1) || GATE_FAILED=true
echo "$PYTHON_OUTPUT"
PYTHON_STATS=$(extract_diff_cov_stats "$PYTHON_OUTPUT")
PYTHON_PCT=$(echo "$PYTHON_STATS" | cut -d'|' -f1)
PYTHON_DETAIL=$(echo "$PYTHON_STATS" | cut -d'|' -f2)
DIFF_COV_SUMMARY="${DIFF_COV_SUMMARY}| Python | ${PYTHON_PCT}% | ${PYTHON_DETAIL} | ${THRESHOLD}% |\n"
fi
# --- Rust ---
if [ -f rust-coverage.xml ]; then
echo "--- Rust incremental coverage ---"
RUST_OUTPUT=$(diff-cover rust-coverage.xml --compare-branch="$COMPARE_BRANCH" --fail-under=$THRESHOLD 2>&1) || GATE_FAILED=true
echo "$RUST_OUTPUT"
RUST_STATS=$(extract_diff_cov_stats "$RUST_OUTPUT")
RUST_PCT=$(echo "$RUST_STATS" | cut -d'|' -f1)
RUST_DETAIL=$(echo "$RUST_STATS" | cut -d'|' -f2)
DIFF_COV_SUMMARY="${DIFF_COV_SUMMARY}| Rust | ${RUST_PCT}% | ${RUST_DETAIL} | ${THRESHOLD}% |\n"
fi
# --- TypeScript ---
TS_COV="openclaw-plugin/coverage/cobertura-coverage.xml"
if [ -f "$TS_COV" ]; then
echo "--- TypeScript incremental coverage ---"
TS_OUTPUT=$(diff-cover "$TS_COV" --compare-branch="$COMPARE_BRANCH" --fail-under=$THRESHOLD 2>&1) || GATE_FAILED=true
echo "$TS_OUTPUT"
TS_STATS=$(extract_diff_cov_stats "$TS_OUTPUT")
TS_PCT=$(echo "$TS_STATS" | cut -d'|' -f1)
TS_DETAIL=$(echo "$TS_STATS" | cut -d'|' -f2)
DIFF_COV_SUMMARY="${DIFF_COV_SUMMARY}| TypeScript | ${TS_PCT}% | ${TS_DETAIL} | ${THRESHOLD}% |\n"
fi
# --- Summary ---
{
echo "### 🚦 Incremental Coverage Gate (new/changed code)"
echo ""
echo "| Language | New Code Coverage | Detail | Threshold |"
echo "|----------|-------------------|--------|-----------|"
echo -e "$DIFF_COV_SUMMARY"
if [ "$GATE_FAILED" = true ]; then
echo ""
echo "> ❌ **Gate FAILED**: New code coverage is below ${THRESHOLD}%. Please add tests for uncovered changes."
else
echo ""
echo "> ✅ **Gate PASSED**: All new code meets the ${THRESHOLD}% coverage threshold."
fi
} >> "$GITHUB_STEP_SUMMARY"
if [ "$GATE_FAILED" = true ]; then
echo "::error::Incremental coverage gate failed: new code coverage < ${THRESHOLD}%"
exit 1
fi
# =========================================================================
# Step 5: Test agentsight (Linux only)
# =========================================================================
test-agentsight:
name: Test agentsight
needs: detect-changes
if: needs.detect-changes.outputs.agentsight == 'true'
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
toolchain: '1.89.0'
- uses: Swatinem/rust-cache@v2
with:
workspaces: src/agentsight
- name: Install eBPF build dependencies
run: |
sudo apt-get update
sudo apt-get install -y clang llvm libbpf-dev libelf-dev elfutils libssl-dev zlib1g-dev pkg-config
sudo apt-get install -y perl
- name: Run tests
run: |
rustup component add rustfmt
cd src/agentsight
cargo test
# =========================================================================
# Step 6: Test tokenless
# =========================================================================
test-tokenless:
name: Test tokenless
needs: detect-changes
if: needs.detect-changes.outputs.tokenless == 'true'
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
toolchain: '1.89.0'
components: 'rustfmt, clippy'
- uses: Swatinem/rust-cache@v2
with:
workspaces: src/tokenless
- name: Check formatting
run: |
cd src/tokenless
cargo fmt -p tokenless-cli -p tokenless-schema -p tokenless-stats -- --check
- name: Lint
run: |
cd src/tokenless
cargo clippy -p tokenless-cli -p tokenless-schema -p tokenless-stats -- -D warnings
- name: Run tests
run: |
cd src/tokenless
cargo test -p tokenless-cli -p tokenless-schema -p tokenless-stats
# =========================================================================
# Step 7: Test ws-ckpt
# =========================================================================
test-ws-ckpt:
name: Test ws-ckpt
needs: detect-changes
if: needs.detect-changes.outputs.ws_ckpt == 'true'
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
components: 'rustfmt, clippy'
- uses: Swatinem/rust-cache@v2
with:
workspaces: src/ws-ckpt/src
- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y btrfs-progs rsync
- name: Check formatting
run: |
cd src/ws-ckpt/src
cargo fmt --all --check
- name: Lint
run: |
cd src/ws-ckpt/src
cargo clippy --workspace -- -D warnings
- name: Run tests
run: |
cd src/ws-ckpt/src
cargo test --workspace