zeph 0.18.3

Lightweight AI agent with hybrid inference, skills-first architecture, and multi-channel I/O
name: telegram-e2e

# Runs Telethon-based E2E tests against the Zeph Telegram channel.
# Manual-only: consumes LLM API tokens, so not triggered on push.
# Builds Zeph from source on each run.
#
# Re-running only failed scenarios: use GitHub's native "Re-run failed jobs"
# button — the single job structure means the whole suite re-runs atomically,
# which is correct because Telegram state (conversation history, /reset) is
# shared across scenarios.

on:
  workflow_dispatch:
    inputs:
      debug:
        description: "Enable extra logging (RUST_LOG=debug)"
        required: false
        default: "false"
        type: choice
        options:
          - "false"
          - "true"
      no_reset:
        description: "Skip /reset before running scenarios (preserves prior conversation state)"
        required: false
        default: "false"
        type: choice
        options:
          - "false"
          - "true"

permissions:
  contents: read

env:
  CARGO_TERM_COLOR: always

# Prevent concurrent runs against the same bot — Telegram state is shared.
concurrency:
  group: telegram-e2e
  cancel-in-progress: false

jobs:
  telegram-e2e:
    name: Telegram E2E (Test DC)
    runs-on: ubuntu-latest
    timeout-minutes: 20
    # Skip if the required secrets are not configured in the repository.
    # secrets context is not available in `if:` expressions; use an env-var
    # sentinel written in a preceding step instead (see "Check secrets" step).
    steps:
      - uses: actions/checkout@v4

      - name: Check secrets
        id: secrets-check
        env:
          TOKEN: ${{ secrets.ZEPH_TELEGRAM_TOKEN }}
          API_ID: ${{ secrets.TELEGRAM_TEST_API_ID }}
          API_HASH: ${{ secrets.TELEGRAM_TEST_API_HASH }}
          SESSION: ${{ secrets.TELEGRAM_TEST_SESSION }}
          BOT_USERNAME: ${{ secrets.TELEGRAM_TEST_BOT_USERNAME }}
        run: |
          missing=()
          [ -z "$TOKEN" ]       && missing+=(ZEPH_TELEGRAM_TOKEN)
          [ -z "$API_ID" ]      && missing+=(TELEGRAM_TEST_API_ID)
          [ -z "$API_HASH" ]    && missing+=(TELEGRAM_TEST_API_HASH)
          [ -z "$SESSION" ]     && missing+=(TELEGRAM_TEST_SESSION)
          [ -z "$BOT_USERNAME" ] && missing+=(TELEGRAM_TEST_BOT_USERNAME)
          if [ ${#missing[@]} -gt 0 ]; then
            echo "Skipping: required secrets not configured: ${missing[*]}"
            echo "skip=true" >> "$GITHUB_OUTPUT"
          else
            echo "skip=false" >> "$GITHUB_OUTPUT"
          fi

      - uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
        if: steps.secrets-check.outputs.skip == 'false'

      # Cache cargo registry/git sources only (no target dir — sccache handles compilation).
      # Uses the same key structure as CI so the populated registry cache is shared.
      - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
        if: steps.secrets-check.outputs.skip == 'false'
        with:
          cache-targets: "false"
          shared-key: "telegram-e2e"

      # sccache caches per-crate compilation results in GHA cache.
      # telegram-e2e uses --profile ci (same as CI lint/build jobs) so sccache
      # entries built by CI are reused here, making reruns nearly instant.
      - uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9
        if: steps.secrets-check.outputs.skip == 'false'

      - name: Set up Python
        if: steps.secrets-check.outputs.skip == 'false'
        uses: actions/setup-python@v5
        with:
          python-version: "3.14.3"
          cache: pip

      - name: Install Telethon
        if: steps.secrets-check.outputs.skip == 'false'
        run: pip install -r scripts/telegram-e2e/requirements.txt

      - name: Restore Telethon session
        if: steps.secrets-check.outputs.skip == 'false'
        # TELEGRAM_TEST_SESSION is the StringSession string produced by get_session.py
        run: |
          mkdir -p .local/testing
          echo "${{ secrets.TELEGRAM_TEST_SESSION }}" > .local/testing/test_session.session

      - name: Build Zeph (telegram feature)
        if: steps.secrets-check.outputs.skip == 'false'
        env:
          RUSTC_WRAPPER: sccache
          SCCACHE_GHA_ENABLED: "true"
        run: cargo build --profile ci --features full --bin zeph

      - name: Write Zeph config
        if: steps.secrets-check.outputs.skip == 'false'
        run: |
          mkdir -p .local/config .local/testing/data .local/testing/debug
          cp config/telegram-test.toml .local/config/telegram-test.toml
          # Switch vault backend to env — vault will read ZEPH_* vars from environment
          sed -i 's|backend = "age"|backend = "env"|' \
            .local/config/telegram-test.toml
          # Patch allowed_users with the test account username
          sed -i 's|allowed_users = \["your_test_username"\]|allowed_users = ["${{ secrets.TELEGRAM_TEST_ACCOUNT_USERNAME }}"]|' \
            .local/config/telegram-test.toml
          echo "--- patched config (allowed_users line) ---"
          grep "allowed_users" .local/config/telegram-test.toml

      - name: Start Zeph (background)
        if: steps.secrets-check.outputs.skip == 'false'
        env:
          ZEPH_TELEGRAM_TOKEN: ${{ secrets.ZEPH_TELEGRAM_TOKEN }}
          ZEPH_OPENAI_API_KEY: ${{ secrets.ZEPH_OPENAI_API_KEY }}
          RUST_LOG: ${{ inputs.debug == 'true' && 'debug' || 'info' }}
        run: |
          ./target/ci/zeph --config .local/config/telegram-test.toml \
            >.local/testing/debug/telegram-session.log 2>&1 &
          echo "Zeph PID: $!"
          # Wait for bot to connect and start long-polling
          sleep 12
          echo "--- last 20 lines of bot log ---"
          tail -20 .local/testing/debug/telegram-session.log || true

      - name: Run E2E scenarios
        if: steps.secrets-check.outputs.skip == 'false'
        env:
          TG_API_ID: ${{ secrets.TELEGRAM_TEST_API_ID }}
          TG_API_HASH: ${{ secrets.TELEGRAM_TEST_API_HASH }}
          TG_BOT_USERNAME: ${{ secrets.TELEGRAM_TEST_BOT_USERNAME }}
          TG_SESSION_PATH: .local/testing/test_session.session
          PYTHONUNBUFFERED: "1"
        run: |
          EXTRA_FLAGS=""
          if [ "${{ inputs.no_reset }}" = "true" ]; then
            EXTRA_FLAGS="--no-reset"
          fi
          python3 -u scripts/telegram-e2e/telegram_e2e.py $EXTRA_FLAGS

      - name: Upload debug log
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: telegram-session-log
          path: .local/testing/debug/telegram-session.log
          retention-days: 7