Files
Genarrative/.gitea/workflows/project-ci.yml
T
kdletters 33f5ad68bf
Project CI / AI game creator shell Rust shard 1/4 (push) Successful in 7m19s
Project CI / AI game creator shell Rust shard 3/4 (push) Successful in 7m26s
Project CI / AI game creator shell Rust shard 4/4 (push) Successful in 7m32s
Project CI / AI game creator shell Rust shard 2/4 (push) Successful in 7m34s
Project CI / AI game creator shell Rust smoke (push) Successful in 1m57s
Project CI / AI game creator shell Rust crates (push) Successful in 3m18s
Project CI / Native shell tests (push) Successful in 10m54s
Project CI / Backend tests (push) Successful in 12m42s
Project CI / Frontend tests (push) Successful in 13m4s
Project CI / AI game creator shell web tests (push) Successful in 5m1s
Project CI / Repository checks (push) Successful in 12m48s
接入 DotCraft Unity 编辑器插件与受控执行链路 (#423)
AGC 原有插件系统无法直接操作已打开的 Unity Editor。本变更增加内置 `agc-unity-editor`,在 Windows x64 / Unity Mono 上支持当前项目探测、连接与 C# 执行,不向 Unity 工程安装 UPM 桥接包。

## 主要变更

- 固定复用 DotCraft.Unity 0.4.3 的 Attach 核心,提供自包含 .NET helper,保留上游许可证、来源及修改记录。
- GUI、Runtime、DirectProject 共用 Runner 执行服务;补齐项目身份、并发、总期限、回执确认与持久不确定状态阻断。
- 现有打开项目入口支持 Unity,按项目类型及开关暴露插件和 Agent 工具。
- Windows 构建准备 helper 并随包分发;插件 JS/Rust 测试接入现有 CI 组,Jenkins 增加 .NET 10 工具链预检。

## 验证

- .NET helper 27 项测试、自包含发布及最小环境协议 smoke 通过。
- Unity 6000.3.7f1 实机验证通过:连接、C# 执行、编译错误修复、断连重连、Domain Reload 后重新握手;真实 Runner 的 ACK、并发拒绝和跨重启阻断通过。
- 宿主 Unity、PluginHost、Cocos、MCP、工具目录与引擎识别定向回归通过;前端类型检查、插件 JS/Rust、CI 配置、格式、编码和文档门禁通过。

Linux CI 不代替 Windows helper/实机验证;发行安装包 UI smoke、其它 Unity 版本和 Unity CoreCLR 未验证。Unity 演示工程中的场景和组件已撤销,不在此 PR 范围内。

---------

Co-authored-by: kdletters <61648117+kdletters@users.noreply.github.com>
Reviewed-on: http://192.168.35.82/git/GenarrativeAI/Genarrative/pulls/423
2026-09-19 12:29:27 +08:00

544 lines
21 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
name: Project CI
on:
push:
branches:
- master
pull_request:
workflow_dispatch:
concurrency:
group: project-ci-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
permissions:
contents: read
env:
CI: 'true'
CARGO_INCREMENTAL: '0'
CARGO_HTTP_MULTIPLEXING: 'false'
CARGO_NET_RETRY: '10'
CARGO_TERM_COLOR: always
NPM_CONFIG_AUDIT: 'false'
NPM_CONFIG_FETCH_RETRIES: '10'
NPM_CONFIG_FETCH_RETRY_FACTOR: '2'
NPM_CONFIG_FETCH_RETRY_MAXTIMEOUT: '60000'
NPM_CONFIG_FETCH_RETRY_MINTIMEOUT: '2000'
NPM_CONFIG_FUND: 'false'
NPM_CONFIG_PREFER_OFFLINE: 'true'
RUSTUP_AUTO_INSTALL: '0'
RUSTC_WRAPPER: ''
CARGO_BUILD_RUSTC_WRAPPER: ''
# job 声明顺序就是 runner 领取顺序,因此把最长尾的客户端 Rust 门禁排在前面,
# 让它在最少的等待下占用并发槽位;其余 job 按时长递减排列。
#
# 客户端(微信壳 / Expo 移动壳 / Tauri 桌面壳 / AI 游戏创作壳)门禁原先全部串在
# `Native shell tests` 一个 job 里,实测 18 分 37 秒。现在按门禁组拆成一个分组一个 job:
# `Native shell tests`(契约 + H5 / 微信 / 移动 / 桌面壳门禁 + 发布构建 smoke)、
# `AI game creator shell web tests`(typecheck + 壳内测试)、`AI game creator shell
# Rust shard 1/4` 到 `4/4`(AGC 壳 bin 单测按名单分 4 片)、`AI game creator shell
# Rust smoke`(agent-run smoke)与 `AI game creator shell Rust crates`(AGC 壳依赖的
# 共享 / 平台 crate 测试)。各自的命令与拆分前逐一对应,本地
# `npm run check:native-shells` 仍是同一条串行序列。
#
# AGC 壳的 bin 单测(2466 条)按名单分 4 片、一片一个 job:片内保持 `--test-threads=1`
# (当年线程并行会互相干扰的是进程内后台锁与异步终态),片与片之间靠 job 级并发摊开。
# 不要改回「一个 job 里多进程并行这几片」:同一容器内它们会争抢共享 HOME、target 与固定
# 临时路径,实测比整套串行还慢。每个分片 job 都会自校验「片并集等于全集且互斥」。
jobs:
# AGC 壳自身的 Rust bin 单测分片,4 片各自独立 job 并发执行、片内仍保持
# `--test-threads=1`。这里不装 npm 依赖:壳 Rust 门禁只用 cargo 与 node 内建模块,
# 也只需要 AGC 壳自己那份锁定依赖。
ai-game-creator-shell-rust-shard-1:
name: AI game creator shell Rust shard 1/4
runs-on: genarrative-ci
steps:
- name: Checkout full history from Gitea
env:
GENARRATIVE_GITEA_FETCH_DEPTH: '0'
GENARRATIVE_GITEA_TOKEN: ${{ github.token }}
run: genarrative-gitea-checkout
- name: Validate preinstalled CI job image and sandbox
run: GENARRATIVE_GITEA_CI_CHECK_RUNTIME=1 bash scripts/check-gitea-ci-job-image.sh
- name: Prepare AI game creator shell Rust dependencies
shell: bash
run: |
set -euo pipefail
# AGC 壳有独立 Cargo.lock,其 path 依赖已含 platform-llm、platform-agent、
# agent-runtime-core 与 shared-contracts,因此只锁这一份 manifest 就能覆盖壳测试
# 与 smoke 的全部第三方依赖;server-rs 那次预热归 crate 级 job,不在这里重复。
for attempt in $(seq 1 5); do
if cargo fetch --locked \
--target x86_64-unknown-linux-gnu \
--manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml; then
break
fi
if [[ "${attempt}" -eq 5 ]]; then
echo 'AI game creator shell Cargo dependency fetch failed after 5 attempts.' >&2
exit 1
fi
sleep $((attempt * 2))
done
- name: Run AI game creator shell Rust shard 1/4
run: npm run check:native-shells:agc-rust-shard-1
ai-game-creator-shell-rust-shard-2:
name: AI game creator shell Rust shard 2/4
runs-on: genarrative-ci
steps:
- name: Checkout full history from Gitea
env:
GENARRATIVE_GITEA_FETCH_DEPTH: '0'
GENARRATIVE_GITEA_TOKEN: ${{ github.token }}
run: genarrative-gitea-checkout
- name: Validate preinstalled CI job image and sandbox
run: GENARRATIVE_GITEA_CI_CHECK_RUNTIME=1 bash scripts/check-gitea-ci-job-image.sh
- name: Prepare AI game creator shell Rust dependencies
shell: bash
run: |
set -euo pipefail
for attempt in $(seq 1 5); do
if cargo fetch --locked \
--target x86_64-unknown-linux-gnu \
--manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml; then
break
fi
if [[ "${attempt}" -eq 5 ]]; then
echo 'AI game creator shell Cargo dependency fetch failed after 5 attempts.' >&2
exit 1
fi
sleep $((attempt * 2))
done
- name: Run AI game creator shell Rust shard 2/4
run: npm run check:native-shells:agc-rust-shard-2
ai-game-creator-shell-rust-shard-3:
name: AI game creator shell Rust shard 3/4
runs-on: genarrative-ci
steps:
- name: Checkout full history from Gitea
env:
GENARRATIVE_GITEA_FETCH_DEPTH: '0'
GENARRATIVE_GITEA_TOKEN: ${{ github.token }}
run: genarrative-gitea-checkout
- name: Validate preinstalled CI job image and sandbox
run: GENARRATIVE_GITEA_CI_CHECK_RUNTIME=1 bash scripts/check-gitea-ci-job-image.sh
- name: Prepare AI game creator shell Rust dependencies
shell: bash
run: |
set -euo pipefail
for attempt in $(seq 1 5); do
if cargo fetch --locked \
--target x86_64-unknown-linux-gnu \
--manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml; then
break
fi
if [[ "${attempt}" -eq 5 ]]; then
echo 'AI game creator shell Cargo dependency fetch failed after 5 attempts.' >&2
exit 1
fi
sleep $((attempt * 2))
done
- name: Run AI game creator shell Rust shard 3/4
run: npm run check:native-shells:agc-rust-shard-3
ai-game-creator-shell-rust-shard-4:
name: AI game creator shell Rust shard 4/4
runs-on: genarrative-ci
steps:
- name: Checkout full history from Gitea
env:
GENARRATIVE_GITEA_FETCH_DEPTH: '0'
GENARRATIVE_GITEA_TOKEN: ${{ github.token }}
run: genarrative-gitea-checkout
- name: Validate preinstalled CI job image and sandbox
run: GENARRATIVE_GITEA_CI_CHECK_RUNTIME=1 bash scripts/check-gitea-ci-job-image.sh
- name: Prepare AI game creator shell Rust dependencies
shell: bash
run: |
set -euo pipefail
for attempt in $(seq 1 5); do
if cargo fetch --locked \
--target x86_64-unknown-linux-gnu \
--manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml; then
break
fi
if [[ "${attempt}" -eq 5 ]]; then
echo 'AI game creator shell Cargo dependency fetch failed after 5 attempts.' >&2
exit 1
fi
sleep $((attempt * 2))
done
- name: Run AI game creator shell Rust shard 4/4
run: npm run check:native-shells:agc-rust-shard-4
# agent-run smoke 会 spawn `cargo run`(走壳自己的 manifest),同样不装 npm 依赖,
# 单独一个 job,免得把已经压到 4 分钟级的片 job 拖长。
ai-game-creator-shell-rust-smoke:
name: AI game creator shell Rust smoke
runs-on: genarrative-ci
steps:
- name: Checkout full history from Gitea
env:
GENARRATIVE_GITEA_FETCH_DEPTH: '0'
GENARRATIVE_GITEA_TOKEN: ${{ github.token }}
run: genarrative-gitea-checkout
- name: Validate preinstalled CI job image and sandbox
run: GENARRATIVE_GITEA_CI_CHECK_RUNTIME=1 bash scripts/check-gitea-ci-job-image.sh
- name: Prepare AI game creator shell Rust dependencies
shell: bash
run: |
set -euo pipefail
for attempt in $(seq 1 5); do
if cargo fetch --locked \
--target x86_64-unknown-linux-gnu \
--manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml; then
break
fi
if [[ "${attempt}" -eq 5 ]]; then
echo 'AI game creator shell Cargo dependency fetch failed after 5 attempts.' >&2
exit 1
fi
sleep $((attempt * 2))
done
- name: Run AI game creator shell agent-run smoke
run: npm run check:native-shells:agc-rust-smoke
# AGC 壳依赖的共享 / 平台和编辑器插件 crate 各自预热独立 manifest,再运行对应测试。
ai-game-creator-shell-rust-crates:
name: AI game creator shell Rust crates
runs-on: genarrative-ci
steps:
- name: Checkout full history from Gitea
env:
GENARRATIVE_GITEA_FETCH_DEPTH: '0'
GENARRATIVE_GITEA_TOKEN: ${{ github.token }}
run: genarrative-gitea-checkout
- name: Validate preinstalled CI job image and sandbox
run: GENARRATIVE_GITEA_CI_CHECK_RUNTIME=1 bash scripts/check-gitea-ci-job-image.sh
- name: Prepare server-rs Rust dependencies
shell: bash
run: |
set -euo pipefail
for attempt in $(seq 1 5); do
if cargo fetch --locked \
--target x86_64-unknown-linux-gnu \
--manifest-path server-rs/Cargo.toml; then
break
fi
if [[ "${attempt}" -eq 5 ]]; then
echo 'server-rs Cargo dependency fetch failed after 5 attempts.' >&2
exit 1
fi
sleep $((attempt * 2))
done
- name: Prepare standalone Rust crate dependencies
shell: bash
run: |
set -euo pipefail
# agent-runtime-core / agent-runtime-orchestration 被 server-rs/Cargo.toml 的
# exclude 排除,不参与上面的 workspace 锁文件,因此上面那次锁定 fetch 覆盖不到它们;
# 而 `npm run ai-game-creator-shell:check:rust:crates` 会用
# `cargo test --manifest-path` 单独跑这两个 crate。不在这里预热的话,这两条测试
# 会在测试阶段自己 `Updating crates.io index`,crates.io 一抖动整条 job 就红
# (见 #327 / PR #316 run 1950)。
# Cocos 插件也使用独立且未提交的锁文件,一并预热。
# 这些 crate 都没有提交 Cargo.lock,所以这里只能做不带锁标志的 fetch:
# 加锁标志会因为缺少锁文件直接失败。生成的 Cargo.lock 落在两个 crate 目录内,
# 已被各自的 .gitignore 忽略,只留在容器里;随后的测试阶段因此能用锁定版本
# 解析,不再触碰 registry index。
for manifest_path in \
server-rs/crates/agent-runtime-core/Cargo.toml \
server-rs/crates/agent-runtime-orchestration/Cargo.toml \
plugins/agc-cocos-editor/native/cocos-editor-bridge/Cargo.toml; do
for attempt in $(seq 1 5); do
if cargo fetch \
--target x86_64-unknown-linux-gnu \
--manifest-path "${manifest_path}"; then
break
fi
if [[ "${attempt}" -eq 5 ]]; then
echo "standalone crate dependency fetch failed after 5 attempts: ${manifest_path}" >&2
exit 1
fi
sleep $((attempt * 2))
done
done
- name: Prepare Unity plugin Rust dependencies
shell: bash
run: |
set -euo pipefail
for attempt in $(seq 1 5); do
if cargo fetch --locked \
--target x86_64-unknown-linux-gnu \
--manifest-path plugins/agc-unity-editor/native/unity-editor-bridge/Cargo.toml; then
break
fi
if [[ "${attempt}" -eq 5 ]]; then
echo 'Unity plugin Cargo dependency fetch failed after 5 attempts.' >&2
exit 1
fi
sleep $((attempt * 2))
done
- name: Run AI game creator shell shared crate gates
run: npm run check:native-shells:agc-rust-crates
backend-tests:
name: Backend tests
runs-on: genarrative-ci
steps:
- name: Checkout full history from Gitea
env:
GENARRATIVE_GITEA_FETCH_DEPTH: '0'
GENARRATIVE_GITEA_TOKEN: ${{ github.token }}
run: genarrative-gitea-checkout
- name: Validate preinstalled CI job image and sandbox
run: GENARRATIVE_GITEA_CI_CHECK_RUNTIME=1 bash scripts/check-gitea-ci-job-image.sh
- name: Resolve comparison base
shell: bash
run: |
set -euo pipefail
base_ref="$(node -e '
const fs = require("node:fs");
const event = JSON.parse(fs.readFileSync(process.env.GITHUB_EVENT_PATH, "utf8"));
process.stdout.write(event.pull_request?.base?.sha ?? event.before ?? "");
')"
if [[ -n "${base_ref}" && ! "${base_ref}" =~ ^0+$ ]]; then
git cat-file -e "${base_ref}^{commit}" 2>/dev/null || {
echo "comparison base commit is unavailable: ${base_ref}" >&2
exit 1
}
else
base_ref="$(git merge-base HEAD origin/master 2>/dev/null || git rev-parse HEAD)"
fi
resolved_base_ref="$(git rev-parse --verify "${base_ref}^{commit}" 2>/dev/null || true)"
head_ref="$(git rev-parse HEAD)"
if [[ "${resolved_base_ref}" == "${head_ref}" ]]; then
resolved_base_ref="$(git rev-parse --verify HEAD^ 2>/dev/null || true)"
fi
if [[ -z "${resolved_base_ref}" ]]; then
echo 'comparison base must resolve to a commit distinct from HEAD.' >&2
exit 1
fi
base_ref="${resolved_base_ref}"
if [[ "${GITHUB_EVENT_NAME:-}" == 'pull_request' ]] \
&& ! git merge-base --is-ancestor "${base_ref}" HEAD; then
echo 'pull request head does not contain the latest base commit; update the branch and rerun CI.' >&2
exit 1
fi
echo "SPACETIME_SCHEMA_BASE_REF=${base_ref}" >> "${GITHUB_ENV}"
- name: Install npm dependencies
run: bash scripts/ci-npm-ci-with-retry.sh
- name: Prepare server-rs Rust dependencies
shell: bash
run: |
set -euo pipefail
for attempt in $(seq 1 5); do
if cargo fetch --locked \
--target x86_64-unknown-linux-gnu \
--manifest-path server-rs/Cargo.toml; then
break
fi
if [[ "${attempt}" -eq 5 ]]; then
echo 'server-rs Cargo dependency fetch failed after 5 attempts.' >&2
exit 1
fi
sleep $((attempt * 2))
done
- name: Check server-rs boundaries
run: npm run check:server-rs-ddd
- name: Run server-rs workspace tests
run: cargo test --locked --workspace --exclude spacetime-module --no-fail-fast --manifest-path server-rs/Cargo.toml
- name: Run SpacetimeDB module unit tests
run: cargo test --locked -p spacetime-module --no-fail-fast --manifest-path server-rs/Cargo.toml
- name: Check api-server targets
run: cargo check --locked -p api-server --all-targets --manifest-path server-rs/Cargo.toml
- name: Check SpacetimeDB module
run: cargo check --locked -p spacetime-module --manifest-path server-rs/Cargo.toml
# 客户端的壳级与契约门禁:静态契约断言、H5 / 微信 / 移动 / 桌面壳运行时门禁,
# 以及依赖发布产物的构建 smoke。
native-shell-tests:
name: Native shell tests
runs-on: genarrative-ci
steps:
- name: Checkout full history from Gitea
env:
GENARRATIVE_GITEA_FETCH_DEPTH: '0'
GENARRATIVE_GITEA_TOKEN: ${{ github.token }}
run: genarrative-gitea-checkout
- name: Validate preinstalled CI job image and sandbox
run: GENARRATIVE_GITEA_CI_CHECK_RUNTIME=1 bash scripts/check-gitea-ci-job-image.sh
- name: Install npm dependencies
run: bash scripts/ci-npm-ci-with-retry.sh
- name: Prepare native Rust dependencies
shell: bash
run: |
set -euo pipefail
for manifest_path in \
apps/desktop-shell/src-tauri/Cargo.toml \
apps/ai-game-creator-shell/src-tauri/Cargo.toml; do
for attempt in $(seq 1 5); do
if cargo fetch --locked \
--target x86_64-unknown-linux-gnu \
--manifest-path "${manifest_path}"; then
break
fi
if [[ "${attempt}" -eq 5 ]]; then
echo "Cargo dependency fetch failed after 5 attempts: ${manifest_path}" >&2
exit 1
fi
sleep $((attempt * 2))
done
done
- name: Run native shell contract gates
run: npm run check:native-shells:contract
- name: Run native shell gates
run: npm run check:native-shells:shells
- name: Run native shell release build smoke
run: npm run check:native-shells:release
- name: Ensure native lockfiles are unchanged
run: git diff --exit-code -- apps/desktop-shell/src-tauri/Cargo.lock apps/ai-game-creator-shell/src-tauri/Cargo.lock
frontend-tests:
name: Frontend tests
runs-on: genarrative-ci
steps:
- name: Checkout source from Gitea
env:
GENARRATIVE_GITEA_FETCH_DEPTH: '1'
GENARRATIVE_GITEA_TOKEN: ${{ github.token }}
run: genarrative-gitea-checkout
- name: Validate preinstalled CI job image and sandbox
run: GENARRATIVE_GITEA_CI_CHECK_RUNTIME=1 bash scripts/check-gitea-ci-job-image.sh
- name: Install npm dependencies
run: bash scripts/ci-npm-ci-with-retry.sh
- name: Run frontend and script tests
run: npm run test
- name: Run BgFilter worker smoke harness tests
run: npm run bgfilter-worker:smoke-test
- name: Validate production health patrol behavior
run: npm run check:production-health-patrol
- name: Validate production API release behavior
run: npm run check:production-api-release
- name: Validate production API deploy behavior
run: npm run check:production-api-deploy
repository-checks:
name: Repository checks
runs-on: genarrative-ci
steps:
- name: Checkout full history from Gitea
env:
GENARRATIVE_GITEA_FETCH_DEPTH: '0'
GENARRATIVE_GITEA_TOKEN: ${{ github.token }}
run: genarrative-gitea-checkout
- name: Validate preinstalled CI job image and sandbox
run: GENARRATIVE_GITEA_CI_CHECK_RUNTIME=1 bash scripts/check-gitea-ci-job-image.sh
- name: Resolve comparison base
shell: bash
run: |
set -euo pipefail
base_ref="$(node -e '
const fs = require("node:fs");
const event = JSON.parse(fs.readFileSync(process.env.GITHUB_EVENT_PATH, "utf8"));
process.stdout.write(event.pull_request?.base?.sha ?? event.before ?? "");
')"
if [[ -n "${base_ref}" && ! "${base_ref}" =~ ^0+$ ]]; then
git cat-file -e "${base_ref}^{commit}" 2>/dev/null || {
echo "comparison base commit is unavailable: ${base_ref}" >&2
exit 1
}
else
base_ref="$(git merge-base HEAD origin/master 2>/dev/null || git rev-parse HEAD)"
fi
resolved_base_ref="$(git rev-parse --verify "${base_ref}^{commit}" 2>/dev/null || true)"
head_ref="$(git rev-parse HEAD)"
if [[ "${resolved_base_ref}" == "${head_ref}" ]]; then
resolved_base_ref="$(git rev-parse --verify HEAD^ 2>/dev/null || true)"
fi
if [[ -z "${resolved_base_ref}" ]]; then
echo 'comparison base must resolve to a commit distinct from HEAD.' >&2
exit 1
fi
base_ref="${resolved_base_ref}"
if [[ "${GITHUB_EVENT_NAME:-}" == 'pull_request' ]] \
&& ! git merge-base --is-ancestor "${base_ref}" HEAD; then
echo 'pull request head does not contain the latest base commit; update the branch and rerun CI.' >&2
exit 1
fi
echo "SPACETIME_SCHEMA_BASE_REF=${base_ref}" >> "${GITHUB_ENV}"
- name: Install npm dependencies
run: bash scripts/ci-npm-ci-with-retry.sh
- name: Run repository checks
run: npm run check:repository-ci
# 客户端的 AI 游戏创作壳前端门禁:typecheck 与壳内测试,不触碰 Cargo。
ai-game-creator-shell-web-tests:
name: AI game creator shell web tests
runs-on: genarrative-ci
steps:
- name: Checkout full history from Gitea
env:
GENARRATIVE_GITEA_FETCH_DEPTH: '0'
GENARRATIVE_GITEA_TOKEN: ${{ github.token }}
run: genarrative-gitea-checkout
- name: Validate preinstalled CI job image and sandbox
run: GENARRATIVE_GITEA_CI_CHECK_RUNTIME=1 bash scripts/check-gitea-ci-job-image.sh
- name: Install npm dependencies
run: bash scripts/ci-npm-ci-with-retry.sh
- name: Run AI game creator shell web gates
run: npm run check:native-shells:agc-web