Skip to content

fix(chat): recover interrupted App turn acceptance #4868

fix(chat): recover interrupted App turn acceptance

fix(chat): recover interrupted App turn acceptance #4868

name: Frontstage Pages
on:
workflow_dispatch:
schedule:
- cron: "37 */6 * * *"
pull_request:
paths:
- ".github/workflows/frontstage-pages.yml"
- "README.md"
- "README.zh-CN.md"
- "apps/presentation/dashboard/**"
- "scripts/chat_bundle.py"
- "scripts/chat_bundle_launcher.mjs"
- "loopx/presentation/chat_bundle.py"
- "apps/presentation/site/**"
- "benchmark/deepswe/behavior-discovery/**"
- "docs/**"
- "loopx/capabilities/**"
- "loopx/extensions/*/README.md"
- "loopx/extensions/*/docs/**"
- "loopx/extensions/*/extension.toml"
- "mkdocs.yaml"
- "docs/book/mkdocs.zh.yaml"
- "docs/book/mkdocs.en.yaml"
- "docs/showcases/**"
- "docs/assets/long-running-loop-openviking-trajectory.png"
- "docs/assets/long-running-loop-ml-experiment-trajectory.png"
- "examples/export-frontstage-share-bundle.mjs"
- "examples/blog-bilingual-index-smoke.mjs"
- "examples/frontstage-share-bundle-smoke.mjs"
- "examples/dashboard-frontstage-browser-smoke.mjs"
- "examples/dev-book-browser-smoke.mjs"
- "examples/readme-star-history-smoke.py"
- "examples/frontstage-stargazer-fetch-smoke.py"
- "examples/dev-book-publication-smoke.py"
- "examples/dev-book-welcome-wagon-smoke.py"
- "examples/goal-channel-frontstage-fixture.py"
- "examples/showcase-catalog-smoke.py"
- "examples/status.example.json"
- "scripts/render-star-history.py"
- "scripts/fetch-github-stargazers.sh"
- "scripts/install-from-github.sh"
push:
branches:
- main
paths:
- ".github/workflows/frontstage-pages.yml"
- "README.md"
- "README.zh-CN.md"
- "apps/presentation/dashboard/**"
- "scripts/chat_bundle.py"
- "scripts/chat_bundle_launcher.mjs"
- "loopx/presentation/chat_bundle.py"
- "apps/presentation/site/**"
- "benchmark/deepswe/behavior-discovery/**"
- "docs/**"
- "loopx/capabilities/**"
- "loopx/extensions/*/README.md"
- "loopx/extensions/*/docs/**"
- "loopx/extensions/*/extension.toml"
- "mkdocs.yaml"
- "docs/book/mkdocs.zh.yaml"
- "docs/book/mkdocs.en.yaml"
- "docs/showcases/**"
- "docs/assets/long-running-loop-openviking-trajectory.png"
- "docs/assets/long-running-loop-ml-experiment-trajectory.png"
- "examples/export-frontstage-share-bundle.mjs"
- "examples/blog-bilingual-index-smoke.mjs"
- "examples/frontstage-share-bundle-smoke.mjs"
- "examples/dashboard-frontstage-browser-smoke.mjs"
- "examples/dev-book-browser-smoke.mjs"
- "examples/readme-star-history-smoke.py"
- "examples/frontstage-stargazer-fetch-smoke.py"
- "examples/dev-book-publication-smoke.py"
- "examples/dev-book-welcome-wagon-smoke.py"
- "examples/goal-channel-frontstage-fixture.py"
- "examples/showcase-catalog-smoke.py"
- "examples/status.example.json"
- "scripts/render-star-history.py"
- "scripts/fetch-github-stargazers.sh"
- "scripts/install-from-github.sh"
permissions:
actions: read
contents: read
pages: write
id-token: write
concurrency:
group: frontstage-pages-${{ github.event_name == 'pull_request' && format('pr-{0}', github.event.pull_request.number) || 'deployment' }}
# Keep production deploys in one lane, but isolate each pull request so an
# unrelated PR or main push cannot cancel its validation run.
cancel-in-progress: true
jobs:
build:
runs-on: ubuntu-latest
timeout-minutes: 40
steps:
- name: Check out repository
uses: actions/checkout@v7
- name: Set up Node
uses: actions/setup-node@v6
with:
node-version: "24"
cache: npm
cache-dependency-path: |
apps/presentation/dashboard/package-lock.json
apps/presentation/site/package-lock.json
- name: Use stable npm
run: npm install -g npm@11
- name: Install dashboard dependencies
working-directory: apps/presentation/dashboard
run: npm ci --include=dev --no-audit --no-fund --registry=https://registry.npmjs.org
- name: Install homepage dependencies
working-directory: apps/presentation/site
run: npm ci --include=dev --no-audit --no-fund --registry=https://registry.npmjs.org
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: "3.11"
cache: pip
- name: Verify packaged Personal Workspace is current
working-directory: apps/presentation/dashboard
run: |
npm run build:chat
python ../../../scripts/chat_bundle.py verify --source
./node_modules/.bin/playwright install --with-deps chromium
npm run smoke:personal-workspace-packaged
- uses: actions/upload-artifact@v7
with:
name: frontstage-chat-bundle-${{ github.sha }}
path: loopx/web/chat/
if-no-files-found: error
retention-days: 7
- name: Install docs dependencies
run: |
python -m pip install --disable-pip-version-check -r docs/requirements-docs.txt
python -m pip install --disable-pip-version-check -e .
- name: Validate public showcase catalog
run: python3 examples/showcase-catalog-smoke.py
- name: Validate README star history
run: python3 examples/readme-star-history-smoke.py
- name: Validate stargazer fetch contract
run: python3 examples/frontstage-stargazer-fetch-smoke.py
- name: Validate Developer Book publication
run: python3 examples/dev-book-publication-smoke.py
- name: Validate bilingual Blog catalog
run: node examples/blog-bilingual-index-smoke.mjs
- name: Validate public-safe frontstage bundle
working-directory: apps/presentation/dashboard
run: npm run smoke:frontstage-share-bundle
- name: Export Pages frontstage artifact
working-directory: apps/presentation/dashboard
run: npm run export:frontstage-share -- --base /loopx/ --out-dir ../../../output/frontstage-pages
- name: Fetch complete GitHub stargazer history
if: github.event_name != 'pull_request'
env:
# Since July 2026, GitHub limits stargazer listing to repository
# admins/collaborators and rejects fine-grained PATs even with
# Metadata: read. Use a classic PAT from an admin/collaborator
# account with only the public_repo scope.
GH_TOKEN: ${{ secrets.STAR_HISTORY_READ_TOKEN }}
run: |
scripts/fetch-github-stargazers.sh \
"$GITHUB_REPOSITORY" \
"$RUNNER_TEMP/loopx-stargazers.json" \
"$RUNNER_TEMP/loopx-stargazers-count"
- name: Generate verified public star history
if: github.event_name != 'pull_request'
run: |
python3 scripts/render-star-history.py \
--repo "$GITHUB_REPOSITORY" \
--input-json "$RUNNER_TEMP/loopx-stargazers.json" \
--expected-count "$(cat "$RUNNER_TEMP/loopx-stargazers-count")" \
--output output/frontstage-pages/site/site-assets/star-history.svg
- name: Verify star history freshness
if: github.event_name != 'pull_request'
run: |
python3 - <<'PY'
import datetime as dt
from pathlib import Path
svg_path = Path(
"output/frontstage-pages/site/site-assets/star-history.svg"
)
svg = svg_path.read_text(encoding="utf-8")
today = dt.datetime.now(dt.timezone.utc).date()
expected = f"updated {today:%b} {today.day}, {today.year}"
if expected not in svg:
raise SystemExit(
f"stale star history: expected {expected!r} in generated SVG"
)
PY
- name: Build documentation site
run: mkdocs build --strict --site-dir output/frontstage-pages/site/docs
- name: Build Chinese Developer Book
run: mkdocs build --strict --config-file docs/book/mkdocs.zh.yaml --site-dir ../../output/frontstage-pages/site/docs/book
- name: Build English Developer Book
run: mkdocs build --strict --config-file docs/book/mkdocs.en.yaml --site-dir ../../output/frontstage-pages/site/docs/book/en
# MkDocs cleans site/docs and intentionally excludes standalone case HTML.
- name: Restore public case pages after documentation builds
run: node examples/export-frontstage-share-bundle.mjs --restore-case-pages --out-dir output/frontstage-pages
- name: Validate analytics opt-in and event boundaries
run: node apps/presentation/site/scripts/analytics-smoke.mjs output/frontstage-pages/site
- name: Configure public website analytics
env:
# Repository variable: public GA4 stream identifier, not an API secret.
# PR previews never send production analytics.
LOOPX_GA_MEASUREMENT_ID: ${{ github.event_name != 'pull_request' && vars.LOOPX_GA_MEASUREMENT_ID || '' }}
run: node apps/presentation/site/scripts/add-analytics.mjs output/frontstage-pages/site
- name: Validate search publication
run: node apps/presentation/site/scripts/seo-smoke.mjs output/frontstage-pages/site --with-docs
- name: Validate homepage navigation and retired links in the published site
run: LOOPX_PUBLIC_SITE_DIR="$PWD/output/frontstage-pages/site" npm --prefix apps/presentation/dashboard run smoke:frontstage-browser
- name: Validate rendered Developer Book
run: python3 examples/dev-book-publication-smoke.py --site-dir output/frontstage-pages/site/docs/book
- name: Validate Developer Book Mermaid rendering in browser
run: node examples/dev-book-browser-smoke.mjs
- name: Validate Developer Book Welcome Wagon
run: python3 examples/dev-book-welcome-wagon-smoke.py --site-dir output/frontstage-pages/site/docs/book
- name: Configure Pages
if: github.event_name != 'pull_request'
uses: actions/configure-pages@v6
with:
enablement: true
- name: Upload Pages artifact
if: github.event_name != 'pull_request'
uses: actions/upload-pages-artifact@v5
with:
path: output/frontstage-pages/site
deploy:
if: github.event_name != 'pull_request'
needs: build
runs-on: ubuntu-latest
timeout-minutes: 15
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Deploy to Pages
id: deployment
uses: actions/deploy-pages@v5