Add local Kokoro TTS server #2031
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Analyze | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| branches: [main] | |
| workflow_dispatch: | |
| # Static-check job only needs to read the checked-out source. | |
| permissions: | |
| contents: read | |
| jobs: | |
| python-lint: | |
| name: Python lint (ruff + async-blocking + no-loguru + no-temperature + prompt-hygiene + i18n-sync + docs-no-relative-paths + api-trailing-slash + frontend-api-trailing-slash) | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| # i18n-sync needs full history to diff against the merge-base of | |
| # origin/main. Other lint steps don't care, but fetch-depth: 0 has | |
| # negligible cost on this repo. | |
| fetch-depth: 0 | |
| - name: Set up Python 3.11 | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: '3.11' | |
| - name: Install uv | |
| uses: astral-sh/setup-uv@v4 | |
| - name: Install ruff | |
| # ruff is the only dev dep we need for the lint job; a full uv sync | |
| # pulls heavy native extensions (playwright, numpy, etc.) that are | |
| # unnecessary for static checks. | |
| run: uv tool install ruff==0.15.4 | |
| - name: ruff check (incl. ASYNC210/220/221/222/251) | |
| run: ruff check . | |
| - name: Forbid blocking calls in async def bodies | |
| # Custom AST checker, broader than ruff's ASYNC* rules: | |
| # * gaps flake8-async doesn't catch: Thread/Process.join, | |
| # queue.Queue.get, raw socket recv/accept/connect | |
| # * extra blocking stdlib/3p calls: PIL.Image.open, | |
| # pyautogui.screenshot, Fernet encrypt/decrypt, shutil.*, | |
| # json.load, plus bare-import forms (sleep, rmtree, urlopen) | |
| # * depth-1 transitive: a sync helper whose body hits any of the | |
| # above, called directly from async, is also flagged. | |
| run: python scripts/check_async_blocking.py | |
| - name: Forbid loguru / structlog / logbook imports | |
| # Logging is unified through utils.logger_config (RobustLoggerConfig). | |
| # Re-introducing a third-party logging frontend fragments the surface | |
| # (formatter, sinks, file naming, multi-process behaviour) and breaks | |
| # plugin/main parity. This check fails the build on any such import. | |
| run: python scripts/check_no_loguru.py | |
| - name: Forbid `temperature=` kwargs on LLM client calls (memory + utils) | |
| # Project policy: do NOT pass `temperature=` to create_chat_llm / | |
| # ChatOpenAI / wrappers in memory_server.py + memory/ + utils/. The | |
| # default (None) omits the field — required for o1/o3/gpt-5-thinking/ | |
| # Claude extended-thinking, and avoids per-call-site temperature drift | |
| # across memory tasks. See memory/__init__.py and .agent/rules/ | |
| # neko-guide.md for the rationale. | |
| run: python scripts/check_no_temperature.py | |
| - name: Enforce prompt-i18n conventions (inline-EN-only + multilang-in-config) | |
| # Two rules in one walker: | |
| # - INLINE_PROMPT_NON_EN: any string at an LLM call site or in a | |
| # module-level *PROMPT/*INSTRUCTION/*SYSTEM constant must be | |
| # English-bodied (CJK ratio <30%). Embedded short examples in | |
| # CJK are allowed under the threshold. | |
| # - I18N_NOT_IN_CONFIG: multi-language dicts (≥2 lang keys, must | |
| # include 'en') belong in config/prompts_*.py, not regular code. | |
| # Per-line `# noqa: <CODE>` suppression supported. See PR #974 for | |
| # the reference incident that motivated this lint. | |
| run: python scripts/check_prompt_hygiene.py | |
| - name: Verify i18n locale files move in lockstep (PR-only) | |
| # Diff-based: when ANY static/locales/*.json or | |
| # frontend/plugin-manager/src/i18n/locales/*.ts changes, ALL files | |
| # in the group must change AND every hunk must occupy the same line | |
| # ranges across all languages. Skipped on direct push to main — | |
| # there's nothing to diff against. | |
| if: github.event_name == 'pull_request' | |
| run: python scripts/check_i18n_sync.py --base "origin/${{ github.base_ref }}" | |
| - name: Forbid relative-up markdown links inside docs/ | |
| # docs/ ships through VitePress with itself as the deploy root; | |
| # any markdown link target starting with '..' resolves outside the | |
| # site and breaks deploy. We've shipped this regression more than | |
| # once — this check fails the build before the next attempt lands. | |
| # Fix is to inline the path as code (`foo/bar.js`) instead of a | |
| # link, or move the referenced content into docs/. | |
| run: python scripts/check_docs_no_relative_paths.py | |
| - name: Forbid trailing-slash route paths on FastAPI decorators (backend) | |
| # Project convention: every backend HTTP/WebSocket endpoint is | |
| # declared WITHOUT a trailing slash. Avoids Starlette's absolute-URL | |
| # 307 redirect under reverse proxies — root cause of the PR #938 | |
| # chara_manager regression: nginx/etc that don't transparently | |
| # forward Host send the redirect Location to 127.0.0.1:<internal>, | |
| # which the LAN browser can't reach (ERR_CONNECTION_REFUSED). | |
| # The lint exempts the literal '/' root page and explicit alias pairs | |
| # (same function carrying both '/foo' and '/foo/'). See | |
| # .agent/rules/neko-guide.md (§"API URL 末尾不带斜杠") and | |
| # main_routers/characters_router.py docstring. | |
| run: python scripts/check_api_trailing_slash.py | |
| - name: Forbid trailing-slash /api/... URL literals (frontend) | |
| # Counterpart to check_api_trailing_slash.py: the backend may declare | |
| # /api/foo without trailing slash, but if frontend code calls | |
| # fetch('/api/foo/') the same 307 → ERR_CONNECTION_REFUSED happens. | |
| # Regex sniffer over static/, frontend/, templates/. Recognises | |
| # prefix builders ('/api/foo/' + id, `/api/foo/${id}`) and exempts | |
| # them; flags only standalone literals ending in '/'. Suppress with | |
| # // noqa: API_TRAILING_SLASH if calling a third-party API that | |
| # genuinely requires the slash. | |
| run: python scripts/check_frontend_api_trailing_slash.py |