Merge branch 'master' into fix/agc-canvas-json
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Successful in 4m50s
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Successful in 6m22s
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Successful in 5m19s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 2m9s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 2m45s
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Successful in 5m38s
Project CI / Frontend tests (pull_request) Failing after 3m50s
Project CI / Repository checks (pull_request) Failing after 4m18s
Project CI / Native shell tests (pull_request) Successful in 8m2s
Project CI / AI game creator shell web tests (pull_request) Failing after 3m31s
Project CI / Backend tests (pull_request) Successful in 8m50s

This commit is contained in:
2026-09-16 22:58:25 +08:00
12 changed files with 340 additions and 27 deletions
@@ -2,6 +2,12 @@
> 用途:记录已经确认、会影响后续开发的长期技术/产品/协作决策。短期讨论不要写在这里。
> 当前口径:历史条目的旧路径、旧版本和已退役对象只用于追溯,不构成现行实现依据;如与当前代码或 `docs/README.md` 冲突,以当前代码和最新专题文档为准。
## 2026-09-16 策划 Agent 工具执行退出项目级写锁并自动接续中断批次
- 背景:策划 Agent 每个 `read_file` / `write_file` / `patch_file` 工具都在执行前竞争全局项目写锁,但同一会话已由 `.agent/design-agent/active.lock` 串行化,工具目标又限定在 `design_artifacts`;项目锁既不覆盖「工具 + 会话 checkpoint」事务,还把进程中断时的 `executing=true` 不确定窗口扩大到等锁与工具执行全程。真机项目出现 `pendingBatch.executing=true``function_call` 无配对 output、UI 只显示工作中且无错误的状态。
- 决策:单次策划工具不再竞争项目级写锁,只保留策划命令锁与既有原子写入;GameAgent / DirectProject 的公共项目锁实现与调用不变。重开项目 hydrate 时,若命令锁可获取且当前批次处于 `executing=true`、当前 call 无 output,则自动续跑原回合:为该 call 补写「执行结果未保存」的工具错误、跳过剩余调用并交回 Provider 自愈;不得重放文件副作用,也不要求用户手动重试。
- 验证:新增定向用例证明中断批次自动补齐工具 output、收到后续 assistant 回复、清空 pendingBatch 并结束原 turn,同时目标文件保持未修改(未重放 `patch_file`);策划 Runtime 定向 14 条、策划工具 3 条通过,`cargo fmt --check``npm run check:encoding``git diff --check` 通过。
- 关联文档:[策划 Agent 生产迁移与工作区浏览](../../technical/【技术方案】策划Agent生产迁移与工作区浏览-2026-09-10.md)。
## 2026-09-16 AGC 同 AppData 多窗口共享 Agent Runner
@@ -8798,3 +8804,11 @@ CI 上 `background_agent_runtime_recovers_stale_running_before_pending_task` 在
- 决策:采用后台继续运行语义。Direct 回合由进程内项目身份锁持有,页面离开不取消;重进项目通过活动回合只读快照与 Thread Manager bootstrap/consume 恢复忙碌态和进度。左上角面板复用同一快照列出正在运行的 Direct 项目并支持进入。
- 边界:快照不写项目文件、不进入公共 API、不跨应用重启恢复;读取失败保留上一份结果并单独提示,不改写成权限或审批失败。身份锁排他性、付费身份和项目写锁不变。
## 2026-09-16 CI 宿主 CPU 上限:Jenkins 16 核 / Gitea Actions runner 12 核
- 背景:`genarrative-station`32 逻辑核)上 Jenkins Built-In Node 与 Gitea Actions runner 共用同一宿主。Jenkins `jenkins.service` 原先没有任何 CPU 限制(`cpu.max=max`),构建期 Web / Api / Stdb 三分支并行(Vitest 8 线程 + 两次默认 32 job 的 cargo)把整机顶到 80%~95%`gitea-runner` 容器 `--cpus=24`75%)在 push 触发的 CI 波峰里实测峰值 24.8~25.3 核,是同一时间窗里更大的单一消耗方。
- 决策:两路 CI 都设硬上限。Jenkins 侧 `systemctl set-property jenkins.service CPUQuota=1600%`16 核 / 50%,覆盖 Built-In Node 上所有子构建,立即生效、无需重启,drop-in 落 `/etc/systemd/system.control/jenkins.service.d/50-CPUQuota.conf`)。runner 侧把 `/opt/gitea-stack/compose.yml``cpus``"24.0"` 改为 `"12.0"`12 核 / 37.5%),并用 `docker update --cpus=12 gitea-runner` 让运行中的容器立即生效,不重建容器、不中断在跑 job。
- 边界:Deploy 阶段在远端 dev / release agent 执行,不受该上限约束。调整只动这两处:`systemctl set-property / revert jenkins.service``docker update --cpus=<n> gitea-runner` 加同步 compose(备份 `/opt/gitea-stack/compose.yml.bak-<时间戳>`)。
- 验证:限速后 `Genarrative-Full-Build-And-Deploy` #289 / #290 SUCCESS;采样期 Jenkins 峰值 10.2~10.5 核、限流不足 2s(可忽略),runner 峰值 12.07 核且持续出现 throttling,整机回落到 2.6%~19.8%。
- 关联文档:[开发运维](../../【开发运维】本地开发验证与生产运维-2026-05-15.md)。
@@ -132,6 +132,8 @@ Runtime 不维护文档版本号,不解析文档版本,不提供版本回退
一轮有多个工具调用时沿用正常工具执行循环。澄清或审批进入等待后,不继续请求 Provider,也不执行同批剩余文件操作;未执行调用明确记录为因等待用户而未执行,不伪造成功结果。恢复历史必须保持工具调用与结果配对,避免出现缺少 tool output 的协议错误。这属于协议与暂停处理,不引入同轮调用次数门禁。
单次策划工具不再竞争项目级写锁;策划命令锁与会话原子写入已保证同一会话内工具按批次顺序执行。若进程在工具执行标记与结果落盘之间中断,重开项目时的只读 hydrate 必须在拿到策划命令锁后自动续跑原回合,为不确定调用补写“执行结果未保存”的工具错误、跳过剩余调用,并把错误交回 Provider 自愈;不得重放文件副作用,也不要求用户手动恢复。
迁移工具集合:
```text
@@ -150,7 +152,7 @@ get_workflow_status
工具使用相对工作区路径。工具执行结果继续通过 Runtime 统一记录和展示,但不向 Agent 暴露宿主绝对路径。
`patch_file` 保留原型按唯一原文匹配修改的语义、换行归一化和缺文件错误。正常工作区写入与删除不逐次请求用户审批;阶段审批不能被复用为文件操作许可。
`patch_file` 保留原型按唯一原文匹配、范围不重叠、全部通过才原子写入的语义、换行归一化和缺文件错误。批量 edits 会一次性完成全部校验,并把未找到、多处匹配、重叠等失败项汇总返回;未找到时同时给出候选行号和可见化缩进提示,帮助 Provider 基于当前文件修正锚点。正常工作区写入与删除不逐次请求用户审批;阶段审批不能被复用为文件操作许可。
`list_resources` 一次返回完整逻辑分类、资源 ID、标题和简介;`read_resource` 按一个资源 ID 读取一个文件。资源描述不增加 `required=true/false` 分类,也不增加引导同轮多次调用的说明。
@@ -670,6 +670,12 @@ Pingora current release 自审脚本 `scripts/ops/pingora-current-release-audit.
`Genarrative-Web-Build` 打包 `web.tar.gz` 前、`Genarrative-Web-Deploy` 解包后都会把 Web 静态目录规范为目录 `755`、文件 `644`。如果前端页面能打开但 public 图片、字体或音频返回 `403 Forbidden`,优先检查当前 `/srv/genarrative/web` 指向的 release 中对应文件权限是否被异常归档为 `600`,临时恢复可对该 release 的 `web` 目录执行目录 `755`、文件 `644` 的权限修正。
### CI 宿主 CPU 上限(Jenkins 16 核 / Gitea Actions runner 12 核)
`genarrative-station` 上 Jenkins Built-In Node 与 Gitea Actions runner 容器共用同一台 32 逻辑核宿主机,两路 CI 都必须有硬上限,避免构建期把机器顶满、让交互用户卡顿。Jenkins 固定 16 核(50%):在宿主执行 `systemctl set-property jenkins.service CPUQuota=1600%`,立即生效且不需要重启 Jenkins,drop-in 落在 `/etc/systemd/system.control/jenkins.service.d/50-CPUQuota.conf`;该配额覆盖 Built-In Node 上所有子构建(Web / Api / Stdb 的 `npm ci`、Vitest、`tsc``cargo` 都跑在这台机器上),Deploy 阶段在远端 `genarrative-dev-deploy` / `genarrative-release-deploy` agent 执行,不受该上限约束。Gitea Actions runner 固定 12 核(37.5%):`/opt/gitea-stack/compose.yml``runner.cpus``"12.0"`,调整运行中的容器用 `docker update --cpus=12 gitea-runner`(不重建容器、不中断在跑 job);需要让容器配置与 compose 完全一致时,先确认 Gitea 没有 `in_progress` run,再 `cd /opt/gitea-stack && docker compose up -d runner`
核验与回滚:`cat /sys/fs/cgroup/system.slice/jenkins.service/cpu.max` 期望 `1600000 100000``docker inspect gitea-runner --format '{{.HostConfig.NanoCpus}}'` 期望 `12000000000``cat /sys/fs/cgroup/system.slice/docker-<runner 容器 ID>.scope/cpu.max` 期望 `1200000 100000`。两处 cgroup 的 `cpu.stat``nr_throttled` / `throttled_usec` 持续增长说明工作负载已经顶到上限,属预期而不是故障。放宽或回滚用 `sudo systemctl set-property jenkins.service CPUQuota=<n>%``sudo systemctl revert jenkins.service``docker update --cpus=<n> gitea-runner`,并同步 `/opt/gitea-stack/compose.yml`(改前先备份该文件)。
## 维护模式只拦截公网流量
Nginx 与 Pingora 在维护 marker 存在时对内网来源绕过整站维护闸,主站页面与静态资源、普通 API、后台页面与 `/admin/api/**`、SpacetimeDB 路由均按非维护状态继续处理;公网应用主站、普通 API、后台和 SpacetimeDB 路由继续返回维护响应。内网范围为 IPv4 loopback / RFC1918 / link-local 和 IPv6 loopback / ULA / link-local。Nginx 只按 TCP `$remote_addr` 判定;Pingora 只按 TCP peer 判定,peer 为 loopback 的同机 Nginx 时才读取 Nginx 强制覆盖的 `X-Real-IP`,绝不能把客户端可伪造的 `X-Forwarded-For` 用作维护放行依据。应用本身的登录、管理员鉴权和其它业务鉴权不变。