# AgentTeam 开发手册

本文档收纳本地开发和回归命令。产品行为以 `SPECS.md` 和 `specs/**/*.md` 为准；用户使用流程以 `docs/public/` 为准。

## 前置依赖

基础开发依赖：

- Go 1.25+；精确 toolchain 见 `go.mod` 和 `desktop/go.mod`
- Node.js；支持版本见 `web/package.json`
- `make`

默认本地 TiDB 开发还需要：

- `tiup`
- MySQL client

当 `AGENTTEAM_DATABASE_DSN` 指向默认本地 TiDB 地址时，`make dev:server` 会启动本地 TiDB playground。如果 `.env` 把 `AGENTTEAM_DATABASE_DSN` 指向远程 TiDB，则会跳过本地 TiDB 启动。Docker 只在明确使用相关部署、集成、外部服务路径，或启用 MeshX Automation server runner 本地执行时需要。

如果需要本地验证消息搜索 FULLTEXT / TiFlash 路径，还需要能访问内网 TiUP mirror `http://10.2.103.2:8989`（`AGENTTEAM_DEV_FEATURES=FTS make dev:server` 会在运行时从这里 bootstrap 本地 search-ready runtime）。

## 首次准备

```bash
make init
```

`make init` 会按需创建 `.env`、下载 Go modules，并准备 `web/node_modules`。在新的 `git worktree` 里，web 依赖会按 Node 版本和 `web/package-lock.json` 复用共享缓存，不需要每个 worktree 都完整执行一次 `npm ci`。

## 本地启动

```bash
make dev:server
```

这个命令会按需启动本地 TiDB playground，显式初始化 schema，然后运行后端和前端开发服务器。运行配置从 `.env` 读取。

`make dev:server` 和 `make dev:desktop` 是仓库根目录的 canonical 开发入口；它们通过独立的 `dev/` Go module 运行同一个开发编排器，共享环境、日志、信号和子进程清理。`make dev:desktop` 对应 repo-owned `dev --desktop` flow。

退出 `make dev:server` 时可以直接按 Ctrl+C；dev helper 会按 section 输出清理进度，并停止本次启动的后端、前端和本地 TiDB playground。`RETAIN_DATA` 会跳过 `tiup clean <tag>`，并在启用 `AGENTTEAM_DEV_FEATURES=MESHX` 时把默认 AGS SQLite DB / git repo 目录放到同一个 retain tag 对应的全局目录（默认 `~/.agentteam/dev-data/<tag>/meshx`，可用 `AGENTTEAM_DEV_RETAIN_DATA_DIR` 覆盖），避免跨 worktree 复用 TiDB 时丢失 MeshX AGS wiki 内容；它不控制进程清理。如果 `RETAIN_DATA` 以 `agentteam-` 开头，它会作为 TiDB playground 的完整 `--tag` 使用，否则本地 tag 为 `agentteam-dev-tidb-${RETAIN_DATA}`。启动本地 TiDB 前，同 tag 的旧 TiUP playground 进程一定会先被强制停止，避免复用残留进程树。

如果这轮开发需要可选本地能力，继续使用唯一入口 `make dev:server`，并通过 `AGENTTEAM_DEV_FEATURES` 选择 profile：

```bash
AGENTTEAM_DEV_FEATURES=MESHX make dev:server
AGENTTEAM_DEV_FEATURES=FTS make dev:server
AGENTTEAM_DEV_FEATURES=MESHX,FTS make dev:server
```

The agent tool settings UI is visible by default during local development. To
hide the account-level agent tool protocol selector and per-agent protocol
override controls, start the dev stack with the server feature flag disabled:

```bash
AGENTTEAM_AGENT_TOOLS_SETTINGS_ENABLED=false make dev:server
```

The public config endpoint exposes the setting unless
`AGENTTEAM_AGENT_TOOLS_SETTINGS_ENABLED=false` is configured.

`AGENTTEAM_DEV_FEATURES=FTS make dev:server` 会：

1. 在当前 worktree 的 `.data/dev/ultra/` 下准备 workspace-local `TIUP_HOME`；
2. 运行时从内网 mirror `http://10.2.103.2:8989` 拉取 root/bootstrap 元数据，并启动一个 search-ready 的本地 `tidb-ultra` playground；
3. 执行 `agentteam-schema init`；
4. 执行 `agentteam-tidb-admin workspace-message-search-ensure-ready`，确保当前开发库里的每个 workspace 都完成 workspace-global message-search readiness；
5. 启动和普通 `make dev:server` 相同的后端、前端与 watch 流程。

停止本地依赖 runtime：

```bash
make stop-dep
```

## Docker 本地启动

### 统一运行时本地验证

`agentteam-server` 的 profile 是唯一拓扑来源：`all` / `workspace` 在启用产品能力时
内嵌 MeshX/AGS，`central` 不构造 MeshX handler、数据库、文件或 worker。进程内
transport 不监听第二个端口，三套 DSN 继续独立。`workspace` worker group fencing
从 AgentTeam placement 动态加载当前 group 的 workspace ID，并在 automation/KB 候选
SQL 的 `LIMIT` 之前过滤；空 group scope 不 claim、retry 或执行其它 group 的工作。
AGS wiki GC、public-repo-cache refresh/cleanup 和 source-mirror 临时目录 cleanup 仍是
全局维护任务；split `workspace` profile 暂不启动它们，避免多 group/滚动副本重复执行。
它们恢复前必须迁入带跨进程 lease 的独立 singleton owner；`all` profile 继续照常执行。
跨 group placement 仍必须沿用先 quiesce、drain 再 move 的维护流程；动态候选过滤
不替代统一 runtime 规范要求的持久化 fence epoch / stale-owner commit fence。

```bash
export AGENTTEAM_SERVER_PROFILE=all
export AGENTTEAM_MESHX_ENABLED=true
export AGENTTEAM_DATABASE_DSN='.../agentteam_core?...'
export MESHX_DATABASE_DSN='.../agentteam_meshx?...'
export MESHX_AGS_DATABASE_DSN='.../agentteam_ags?...'
go run ./cmd/agentteam-server
```

默认 `docker-compose.local.yml` 只启动一个 AgentTeam/MeshX runtime owner。统一发版不
携带 standalone binary、compose 或拓扑开关；回滚恢复旧版本的完整 release bundle。
所有 DDL 只由 tools 镜像中的 `agentteam-schema init` 执行。

SaaS split topology 的本地 Compose 必须同时验证旧客户端数据面和唯一 worker owner；
仓库只提供 final-only 的 central/Gateway/双 workspace group Compose，不提供只验证
readiness 的伪 split harness 或 runtime compatibility mode。维护切换先 quiesce 并排空旧
release 的 standalone workers，再启动新 workspace owners 和最终 Gateway 路由；失败时
先停新 owners，再恢复完整旧 release bundle。统一版本的所有 profile 都不读取或校验
已退役的 `AGENTTEAM_MESHX_BASE_URL`；遗留值会被静默忽略，不能恢复 standalone 路由。

`docker-compose.delivery-split.yml` 是可交互的完整 split 本地集群，而不只是数据面
fixture。它包含 AgentTeam Web、AgentTeam Ops、AgentTeam MaaS、TiDB/DDL，以及 central、Gateway 和
两个独立 workspace owner。用户只通过 <http://localhost:28083> 访问 AgentTeam；Ingress 将
API、WebSocket、raw 和 Git smart-HTTP 统一交给 Gateway，central/workspace 不发布宿主机
业务端口。AgentTeam Ops 位于 <http://localhost:28087>，AgentTeam MaaS health/API 位于
<http://localhost:28088>，TiDB 调试端口为 `127.0.0.1:24003`。端口分别可通过
`AGENTTEAM_DELIVERY_SPLIT_PORT`、`AGENTTEAM_DELIVERY_SPLIT_OPS_PORT`、
`AGENTTEAM_DELIVERY_SPLIT_MAAS_PORT` 和 `AGENTTEAM_DELIVERY_SPLIT_TIDB_PORT` 覆盖。

> **仅限本地 smoke，禁止作为 staging/production 部署清单。** Compose 中的
> `delivery-fixtures`、`repository-fixtures` 和 Ops seed 会插入合成用户、token、
> workspace、placement、machine、repository 与 Git 内容。线上只运行正式 schema
> job，并通过受审控制面配置与 placement migration/verify 建立真实路由状态；不得复制
> 这些 fixture 行或凭据。

```bash
docker compose -f docker-compose.delivery-split.yml up -d --build
./scripts/verify-delivery-split.sh
```

split Compose 与完整本地栈一样，默认从相邻的 `agentteam-maas`、`agentteam-ops` checkout 构建；
非默认目录使用 `AGENTTEAM_MAAS_SOURCE_DIR`、`AGENTTEAM_OPS_SOURCE_DIR` 覆盖。两个 workspace
owner 各自挂载独立 volume；central 不挂载 MeshX 数据卷，也不持有 MeshX runtime。
`workspace-default` 是本地集群的默认 workspace group，也是未显式配置时私有 identity
resolver 与 MeshX admin 的服务位置；它不是公开 AGS global owner，也不是 unresolved
workspace 请求的 fallback。AgentTeam Ops 的 placement admin 通过容器内网直连 central，
MeshX admin 通过容器内网直连 Workspace Service，`/internal/admin/*` 始终不会经过公开
Gateway。split Gateway 对 discovery/OpenAPI、meta/rate-limit、OAuth/device/connected、
OIDC、public-agent 与 analytics 等非 AgentTeam 产品兼容路径统一返回 404。验证脚本同时锁定
这些零上游拒绝，以及已单独批准的 workspace/product 路由行为。

split Gateway 不暴露 `/api/graphql` 和 repository management API；这些路径稳定返回
404。all profile 仍保留原 AGS surface，未来 split 若需要这些协议，应单独定义存储与
owner contract，不能由 Gateway 猜测 workspace。

`MESHX_AGS_EMBEDDING_DIMENSIONS`（兼容别名 `EMBEDDING_DIMENSIONS`）沿用 AGS
既有语义。统一 runtime 不新增搜索/vector schema contract，也不在 resident replica
中执行 DDL；部署前由 tools `init` 复用 main 已有的 AGS migration 行为。

从 standalone MeshX 的旧数据卷升级时，不能用空的 AgentTeam 数据卷替代旧卷：AGS bare
repositories 和 MeshX 派生文件不在 TiDB 中。停止所有 AgentTeam/MeshX/AGS writer，并对三库
与旧数据卷建立同一恢复点后，将旧、新数据根分别挂载到临时容器，然后执行：

```bash
scripts/migrate-unified-runtime-volume.sh --expected-repositories "$EXPECTED_AGS_REPOSITORIES" copy /mnt/old-meshx-data /mnt/new-agentteam-data
scripts/migrate-unified-runtime-volume.sh --expected-repositories "$EXPECTED_AGS_REPOSITORIES" verify /mnt/old-meshx-data /mnt/new-agentteam-data
```

命令拒绝非空目标，不覆盖或删除旧数据，并校验文件 digest、AGS bare repository
完整性、HEAD、refs 与 object inventory。只有 verify 成功后才能启用新数据根；旧卷须
保留到回滚窗口结束。`EXPECTED_AGS_REPOSITORIES` 必须来自升级前数据库或备份 inventory；脚本
默认拒绝空的 AGS repository 目录，只有已独立确认从未存过 repository 的环境才能显式
使用 `--allow-empty`。

部署镜像可直接验证同一入口：

```bash
docker build -f Dockerfile.server -t agentteam-server:unified-runtime .
docker run --rm --env-file /path/to/unified-runtime.env -p 3001:3001 \
  -v agentteam-unified-data:/data agentteam-server:unified-runtime
curl -fsS http://127.0.0.1:3001/readyz
```

`Dockerfile.server` 已包含 embedded MeshX 模块、Git HTTP backend、MeshX/AGS
数据目录，以及 `agentteam-schema`、`agentteam-tidb-admin` 两个 one-shot 工具入口。
服务与工具共享同一版本镜像，但生命周期保持分离：常驻 replica 仍只启动
server 入口，DDL 和管理命令只能由显式 Job/operator 调用。Extension Package
由未来的 Agent 按 `specs/extension/` 生成，不在运行镜像中提供 authoring CLI：

```bash
docker run --rm --env-file /path/to/unified-runtime.env \
  agentteam-server:unified-runtime /app/bin/agentteam-schema init
```

国内网络本地构建常驻镜像时可仅在构建期覆盖 Debian 镜像源；生产和 CI 未传参数时仍使用官方源：

```bash
docker build -f Dockerfile.server \
  --build-arg DEBIAN_MIRROR=http://mirrors.tuna.tsinghua.edu.cn/debian \
  --build-arg DEBIAN_SECURITY_MIRROR=http://mirrors.tuna.tsinghua.edu.cn/debian-security \
  -t agentteam-server:unified-runtime .
```

```bash
make docker-dev
```

`make docker-dev` 使用 `Dockerfile.dev` 构建开发容器，并通过 `docker-compose.dev.yml` 把宿主机当前 worktree 的 `.data/` 挂载到容器内：

- `.data/` -> `/workspace/agentteam/.data`
- `.data/tiup` -> `/root/.tiup`

Docker dev 在未显式设置 `RETAIN_DATA` 时默认使用 `RETAIN_DATA=docker-dev`，因此 TiUP playground 使用稳定 tag `agentteam-dev-tidb-docker-dev`，停止容器时不会执行 `tiup clean` 清理本地 TiDB 数据。这样重新运行 `make docker-dev` 会复用数据库、注册信息、TiUP 元数据和已下载的 TiUP 组件。需要重建 Docker dev 数据时，先停止容器，再删除宿主机 `.data/tiup/` 或覆盖 `RETAIN_DATA` 使用新的 tag。

### Docker Compose 完整本地栈

需要同时验证 AgentTeam、MeshX、AgentTeam MaaS 和 AgentTeam Ops 时，使用
`docker-compose.local.yml`。默认目录结构如下：

```text
~/work/code/
├── agentteam/
├── agentteam-maas/
└── agentteam-ops/
```

从 AgentTeam checkout 启动：

```bash
cd ~/work/code/agentteam
docker compose -f docker-compose.local.yml up -d --build
docker compose -f docker-compose.local.yml ps
```

首次构建需要访问 Docker Hub，并下载 Go 和 npm 依赖。长期服务应显示
`Up` / `healthy`；`databases`、`agentteam-schema` 和 `agentteam-ops-seed` 显示
`Exited (0)` 表示初始化成功。检查服务：

```bash
curl -fsS http://localhost:28080/readyz
curl -fsS http://localhost:28081/readyz
curl -fsS http://localhost:28082/healthz
```

- AgentTeam：<http://localhost:28080>
- AgentTeam Ops：<http://localhost:28081>，本地账号
  `admin@agentteam.local` / `agentteam-local-admin`
- AgentTeam MaaS：<http://localhost:28082>
- TiDB：`127.0.0.1:24000`

完整本地栈会用独立的 `searxng` Compose service 启动 SearXNG，并把
`agentteam-server` 的 Knowledge Q&A Web Search provider 指向
`http://searxng:8080`。SearXNG 不打包进 `agentteam-server` 镜像，也不在
`agentteam-server` 进程内运行；默认只暴露给 Compose 内部网络。如需从宿主机直接调试
SearXNG，可临时给 `searxng` service 添加本地 `ports` 映射；如需固定版本，可用
`SEARXNG_IMAGE` 覆盖镜像 tag。

完整本地栈显式设置 `MESHX_KNOWLEDGE_QA_DEBUG_MODEL_OUTPUT=true`。因此失败的
Knowledge Q&A turn 会在 owner thread 中显示最后一次 MaaS 原始响应的有界
Debug 摘录，便于诊断函数调用协议和探索预算问题。该开关默认关闭；原始响应可能
包含 reasoning、函数参数或知识内容，不应在共享或生产环境中开启。

若两个相邻仓库不在默认位置，通过环境变量覆盖构建上下文：

```bash
AGENTTEAM_MAAS_SOURCE_DIR=/path/to/loop-maas \
AGENTTEAM_OPS_SOURCE_DIR=/path/to/loop-ops \
docker compose -f docker-compose.local.yml up -d --build
```

Repo import 默认由 MeshX 容器直连上游 Git。不要默认配置宿主机代理；只有宿主机
确实运行 HTTP 代理时，才取消 compose 中
`MESHX_KB_SOURCE_MIRROR_OUTBOUND_PROXY` 的注释并填写实际端口。私有仓库仍需在导入时提供 read token。

停止服务但保留数据：

```bash
docker compose -f docker-compose.local.yml down
```

删除本地栈及其 volume 数据：

```bash
docker compose -f docker-compose.local.yml down -v
```

如 Docker Hub 返回 `registry-1.docker.io ... EOF`，先修复 Docker Desktop
的 registry 网络或代理，再重试同一条启动命令。

如果 `AGENTTEAM_DATABASE_DSN` 已经显式指向远程 TiDB，`AGENTTEAM_DEV_FEATURES=FTS make dev:server` 会跳过本地 runtime bootstrap，只复用该 DSN 并继续执行 schema/init + readiness。

本地开发 helper 默认把注册模式设为 open，方便从 Web UI bootstrap 新实例。离开 dev helper 的普通 runtime config 仍默认是 invite-only，除非显式配置。

本地未显式配置 master key 时，后端会按需生成并复用本地凭据加密 key 文件：

- `AGENTTEAM_APP_CREDENTIALS_MASTER_KEY` / `AGENTTEAM_APP_CREDENTIALS_KEY_FILE`：workspace 级 App、GitHub/Gitea、App connection、Agent LLM API key 等凭据；默认文件是 `.data/secrets/app_credentials_master_key`。
- `AGENTTEAM_ORG_CREDENTIALS_MASTER_KEY` / `AGENTTEAM_ORG_CREDENTIALS_KEY_FILE`：组织/飞书侧凭据；默认文件是 `.data/secrets/org_credentials_master_key`。历史上由 org key 加密的 App/Agent 凭据会作为 legacy fallback 读取，新写入使用 app credential key。

如果用共享开发库或类生产环境，请把这些 key 文件放在持久目录并备份，或者用 base64 编码的 32 字节 `*_MASTER_KEY` 环境变量固定；不要提交 key 文件。

Extension provider 请求默认只允许固定 HTTPS origin 解析到公开、全局可路由 IP。
当前不提供全局私网例外，也不存在关闭 SSRF/DNS rebinding 检查的全局开关。
未来需要访问内网 provider 时，在 per-Extension/per-Definition 层提供显式策略；
由 #t2465 持续跟进。

`agentteam-server` 默认在 `127.0.0.1:2112/metrics` 暴露 Prometheus 指标。本地启动后可以用下面的命令验证基础指标：

```bash
curl http://127.0.0.1:2112/metrics
```

主 HTTP 入口会记录 `agentteam_http_*` 请求指标。`/healthz` 和 `/readyz` 不进入请求延迟统计，避免健康检查污染分位数。

HTTP 请求会带有 `X-Request-ID` 响应头；调用方传入合法的 `X-Request-ID` 时服务端沿用，否则服务端生成新的 UUID。使用 request context 写出的结构化日志会包含 `request_id` 字段。

Tracing 默认不向外发送数据。需要本地接入 OpenTelemetry Collector 时，配置 OTLP/HTTP base URL，例如：

```bash
AGENTTEAM_OBSERVABILITY_OTLP_ENDPOINT=http://127.0.0.1:4318
```

服务端会向该 base URL 下的 `/v1/traces` 发送 span。需要鉴权时可额外设置 `AGENTTEAM_OBSERVABILITY_OTLP_HEADERS='Authorization=Bearer <token>'`。不配置 endpoint 时仍会保留 no-op tracer 边界，不产生网络导出。

DB span 默认不带 SQL 文本。需要在 Grafana Tempo 里排查具体 SQL 时，可以显式设置：

```bash
AGENTTEAM_OBSERVABILITY_DB_STATEMENT_ENABLED=true
```

开启后，`otelsql` 会在 DB span 上记录传给 `database/sql` 的 SQL 字符串：默认字段通常是 `db.statement`；如果同时设置 `OTEL_SEMCONV_STABILITY_OPT_IN=database`，字段会变成稳定语义约定的 `db.query.text`。Tracing 层不会解析、脱敏或重新模板化这个字符串。只有确认相关查询构造都使用 `?` 占位符、没有把用户输入或 secret 拼进 SQL 文本时，才应该开启这个开关。`database/sql` 参数列表不会作为 span 属性记录，`otelsql` SQL commenter 也仍保持关闭。

容器部署需要让 Prometheus 从容器网络访问该端口时，把 `AGENTTEAM_OBSERVABILITY_METRICS_ADDRESS` 显式设为 `:2112`，并使用 Compose `expose` 或等价的内部网络暴露，不要把 metrics 端口直接 publish 到公网。

如果 schema 变更后需要重建本地数据：

```bash
make reset-dep
make dev:server
```

## Electron Desktop 本地联调

根 `web/` 是 Browser 与 Desktop 的唯一业务前端。macOS / Windows 的
Desktop host 是 `desktop/electron/`；Linux 只发布 headless Go daemon/CLI，
不发布 Electron 或本地 browser UI。直接在当前 checkout 调试完整
Desktop：

```bash
make dev:desktop
```

该命令运行 repo-owned `dev --desktop` 编排：构建 pure Go sidecar，启动根
`web/` 的 Desktop Vite entry，构建并启动 Electron main/preload，并把
显式 sidecar path 与 Vite renderer URL 交给 Electron。Go 源码变化会重建
并重启 sidecar；renderer 使用 Vite HMR。生产包不加载 Vite，而是从
`app.asar` 通过 immutable `app://agentteam/` 加载 renderer。

### 本地 Token 消耗诊断

Token 诊断属于正式 `agentteam` CLI，但不进入普通 Desktop 导航或 Settings。先启动
AgentTeam Desktop 或 `agentteam daemon`，再运行：

```bash
# 首次运行会自动开启本次 daemon lifetime 的详细采集；历史不会补录：
agentteam token analyze --window 1d

# 复现目标任务后再次分析，并可导出脱敏报告：
agentteam token analyze --window 1d --run <run-ref> \
  --case exec-help --changed-variable platform.agentteam.exec \
  --workspace-revision <revision> --tool-profile <profile> \
  --cache-state warm --outcome success \
  --output /tmp/token-baseline.json

# 查看本地操作界面；页面顶部只有一个采集开关：
agentteam token ui
agentteam token ui --no-open

# 输出当前证据后关闭详细采集：
agentteam token analyze --stop

# 比较两份脱敏报告：
agentteam token compare \
  --baseline /tmp/token-baseline.json \
  --candidate /tmp/token-candidate.json
```

详细高基数采集默认关闭，不使用环境变量或 Desktop Settings；daemon 重启后也会
恢复关闭。关闭时普通 per-Agent input/cache/output 总量仍继续记录。开启时，仍在
运行的 Session 会补记无正文的 launch Context ledger，但之前发生的 delivery 和
usage 不会补录。

`agentteam token ui` 使用随机端口且只监听 `127.0.0.1`。每个 UI 进程生成一次性
访问 token，拒绝非 loopback 请求和跨站 Origin，不启用 CORS，也不加载 CDN、
遥测或外部资源。报告不包含 Prompt、消息、任务、tool output 或源码位置；导出
时还会移除 Agent/Run 明细、本地 Agent 名称和 exact runtime/model。Agent runtime 可以显式执行
`agentteam token analyze --format json`，但只能读取自身 Agent/Run，不能开关采集或
打开 machine-wide UI；关闭时报告会明确提示由 Human 从 Token UI 或本机 shell
开启。`24h` 可作为 `1d` 的输入别名。Run-scoped export 是带 `snapshotAt` 的
point-in-time 报告；常驻 interactive Run 可保持 `active`，但必须在目标任务
outcome 已确定后导出，后续刷新不会修改已保存 artifact。
受控 compare 要求 baseline/candidate 使用相同 case、Workspace revision、tool
profile、cold/warm cache 与 outcome，并只声明一个 changed variable。

renderer 通过 typed `window.agentteamDesktop` preload facade 取得已验证 daemon
base URL 与短期 UI session；它不拥有 Node integration、generic IPC 或
daemon control token。`agentteam://` 是操作系统注册的 external deep-link
scheme，`app://` 仅供 Electron 内部 renderer 使用。

需要分别验证构建边界时：

```bash
# 从仓库根目录构建正式入口：
make build:daemon   # 输出 desktop/bin/agentteam，纯 Go daemon/CLI
make build:server   # 输出 bin/agentteam-server
make build:desktop  # 输出当前平台的 Electron GUI App/安装包

# Desktop 子模块也可以直接构建：
cd desktop
make desktop        # Electron GUI App/安装包
make build-daemon   # 纯 Go daemon/CLI
make build-renderer # 仅 renderer staging tree

AGENTTEAM_DESKTOP_PLATFORM=darwin \
AGENTTEAM_DESKTOP_ARCH=arm64 \
AGENTTEAM_DESKTOP_VERSION=0.0.0-dev \
  make build-electron

cd electron
npm test
npm run typecheck
```

`make build-daemon` 是 pure-Go 路径，不需要 Node 或 renderer 产物。
根目录的 `make build:daemon` 是该目标的稳定转发入口；`make build:server`
独立构建 AgentTeam Server。`make build:desktop` 转发到 `desktop/` 下的
`make desktop`，根据当前 macOS/Windows 平台和架构构建真正的 Electron
GUI 分发产物。通过 `AGENTTEAM_DESKTOP_VERSION=x.y.z` 可以显式指定版本；未指定时
构建从当前 checkout 最近可达的 Git tag 读取版本并去掉前导 `v`，然后将同一
版本注入 Electron package、Go sidecar 和 staging manifest。没有可用 tag 时
构建会失败，不会生成虚构的 fallback 版本。
`make build:desktop` 和 `desktop/` 下的 `make desktop` 默认从 OpenCode
GitHub Releases 的 `latest` 重定向解析一次最新稳定版本，并在本次构建中
将该确定版本用于 runtime 下载、sidecar 版本信息和 staging manifest。
需要可复现构建、离线构建或回滚时，显式设置
`AGENTTEAM_MANAGED_OPENCODE_VERSION=x.y.z` 即可跳过 latest 查询。
`make build-renderer` 输出 Electron renderer staging tree；
`make build-electron` 构建 main/preload，并 staging/verify Go sidecar、
managed OpenCode 与 renderer；把示例 target/version 换成实际验证目标。
Electron release target 是 macOS arm64/x64 与 Windows arm64/x64；Windows
arm64 使用 `AGENTTEAM_DESKTOP_PLATFORM=win32 AGENTTEAM_DESKTOP_ARCH=arm64`，并与
`opencode-windows-arm64` 及 arm64 Go sidecar 一起 staging。
macOS / Windows production package 把
main/preload/renderer 放入 `app.asar`，把 `agentteam[.exe]` 与 managed OpenCode
作为 `extraResources`。Browser build 继续使用 `cd web && npm run dev` 与
`npm run build:web`。

## TiDB 数据导出与导入

仓库内置了一组基于 `tiup dumpling` / `tiup tidb-lightning` 的逻辑数据导出导入脚本：

```bash
scripts/tidb-export.sh /tmp/agentteam-dump
scripts/tidb-import.sh /tmp/agentteam-dump
```

约定如下：

- 脚本围绕 `AGENTTEAM_DATABASE_DSN` 工作，默认导出该 DSN 对应 database 的 schema + data SQL dump
- `scripts/tidb-import.sh` 只支持导入带 Dumpling schema 文件的 dump；老的 data-only dump 需要重新导出
- 导入时不再先跑 `agentteam-schema init/import-init/verify`；脚本会生成临时 Lightning config，把 dump 里的源 database 名字 remap 到目标 `AGENTTEAM_DATABASE_DSN` database，然后直接由 Lightning 建库、建表并导入数据
- 目标库应为空库或新库，避免和 dump 中的 schema / data 冲突

常见用法：

```bash
# 导出当前 AgentTeam core database 的 schema + data
AGENTTEAM_DATABASE_DSN='root@tcp(127.0.0.1:4000)/agentteam_core?tls=false' \
scripts/tidb-export.sh /tmp/agentteam-dump

# 导入到一个全新 database；脚本会把 dump 内的 schema 名重写成 agentteam_restore
AGENTTEAM_DATABASE_DSN='root@tcp(127.0.0.1:4000)/agentteam_restore?tls=false' \
scripts/tidb-import.sh /tmp/agentteam-dump

# 导入完成后可选做一次 schema 校验
AGENTTEAM_DATABASE_DSN='root@tcp(127.0.0.1:4000)/agentteam_restore?tls=false' \
go run ./cmd/agentteam-schema verify
```

可调参数：

- `AGENTTEAM_TIDB_DUMPLING_COMPONENT` / `AGENTTEAM_TIDB_LIGHTNING_COMPONENT`：覆盖默认的 `tiup` component 名称，便于固定版本，例如 `dumpling:v8.5.5` 或 `tidb-lightning:v8.5.5`
- `AGENTTEAM_TIDB_EXPORT_THREADS`、`AGENTTEAM_TIDB_EXPORT_ROWS`、`AGENTTEAM_TIDB_EXPORT_FILE_SIZE`：调优 dumpling 导出并发与分片

当前版本的 TLS 支持刻意保守：

- `tidb-export.sh` 默认按 `tls=false` 路径设计
- 如果需要证书型 TLS，可通过 `AGENTTEAM_TIDB_TLS_CA_PATH`、`AGENTTEAM_TIDB_TLS_CERT_PATH`、`AGENTTEAM_TIDB_TLS_KEY_PATH` 传给 dumpling / tidb-lightning

## MeshX 本地联调

`AGENTTEAM_DEV_FEATURES=MESHX make dev:server` 只启动 unified `agentteam-server` 与 Vite，
并自动设置 `AGENTTEAM_MESHX_ENABLED=true`。`meshx/` Go 代码变更会重建同一个
`agentteam-server`；不会构建或拉起第二个 resident：

```env
AGENTTEAM_SERVER_PROFILE=all
AGENTTEAM_MESHX_ENABLED=true
AGENTTEAM_MESHX_KNOWLEDGE_BASES_ENABLED=true
AGENTTEAM_PUBLIC_BASE_URL=http://localhost:3001
MESHX_DATABASE_DSN=root@tcp(127.0.0.1:4000)/agentteam_meshx?multiStatements=true&parseTime=true&loc=UTC
MESHX_AGS_DATABASE_DSN=sqlite://.data/meshx/ags.db?_foreign_keys=on
MESHX_AGS_GIT_REPO_DIR=.data/meshx/ags-repos
MESHX_KB_PUBLIC_REPO_CACHE_DIR=.data/meshx/public-repo-cache
MESHX_AUTH_TRUSTED_JWT_CLAIMS=true
MESHX_AUTH_TRUSTED_JWT_CLAIMS_PROTECTED_LISTENER=true
```

KB Wiki generation 默认关闭，只有 operator 显式启用后，server-owned worker 才会
运行。本地联调时，先构建 `agentteam-insight`，再在启动 MeshX profile 前配置 binary 与
隔离 work directory：

```bash
mkdir -p .data/bin
(cd kits/agentteam-insight && go build -o ../../.data/bin/agentteam-insight .)
export MESHX_KB_WIKI_GENERATION_ENABLED=true
export MESHX_KB_WIKI_GENERATION_BINARY="$PWD/.data/bin/agentteam-insight"
export MESHX_KB_WIKI_GENERATION_WORK_DIR="$PWD/.data/meshx/wiki-generation"
# 可选，默认 2h。
export MESHX_KB_WIKI_GENERATION_TIMEOUT=2h
```

worker 只执行显式且带 revision fence 的 setup request；不会扫描全部 Source，也不会在
mirror sync 后自动生成内容。

`MESHX` profile 下本地 TiDB 会确保 `agentteam_core`、`agentteam_meshx` 数据库存在；AGS
默认使用本地 SQLite，启用 `FTS` 或 `TIDB_ZERO` 时切换为 `agentteam_ags` TiDB。
MeshX 与 AGS 对外地址统一使用 `AGENTTEAM_PUBLIC_BASE_URL`；AgentTeam forwarder 固定使用
进程内 handler transport。Git、Automation 与 REST 客户端都只访问 AgentTeam public URL。

如果只想开放 MeshX Memory / Skills / Roles，而不显示 KB 入口，保持
`AGENTTEAM_MESHX_ENABLED=true`，并设置 `AGENTTEAM_MESHX_KNOWLEDGE_BASES_ENABLED=false`。

`MESHX_AUTOMATION_CREDENTIALS_MASTER_KEY` 在创建/更新 Automation secret，以及 server-managed runner 执行引用 `${{ secrets.NAME }}` 的 automation env 时需要；它必须是 32 字节随机值的 base64 编码。未配置时，secret metadata 读取和删除仍可用，但写入 secret 或执行引用 secret 的 job 会 fail closed。

`MESHX_PERSONAL_ACCESS_TOKEN_CREDENTIALS_MASTER_KEY` 加密用户保存的外部导入访问令牌，用于 KB mirror import 的 saved-token flow；它同样必须是 32 字节随机值的 base64 编码。未配置时，已保存令牌 metadata 仍可读取和删除，但创建、轮换或使用 saved token 会 fail closed。

MeshX 只读验证 AgentTeam workspace membership 时复用 `AGENTTEAM_DATABASE_DSN`。启用 KB mirror
saved-token 定时同步时，MeshX 会先确认 token owner 仍是该 workspace 的 active human
member，再结合 KB grant/default-role 检查决定是否继续访问外部 Git remote。Memory/Skill
的计费配额由 AgentTeam 检查：浏览器发起的 MeshX 创建请求在 AgentTeam forwarder 里检查，AgentTeam
后端通过 MeshX client 编排的角色实现流程会在调用 MeshX 前检查。MeshX 只提供 resource
count 和 realization impact，不读取 AgentTeam billing/plan/grant 表。

MeshX V1 不再依赖 AgentTeam→MeshX shared-secret service token。经 AgentTeam gateway 部署时，MeshX 通过 `MESHX_AUTH_TRUSTED_JWT_CLAIMS=true` 显式信任 `X-AgentTeam-Verified-JWT` 中的 AgentTeam-verified claims；AgentTeam gateway 会先清理客户端传入的内部身份 header，再用已认证的 AgentTeam session 生成可信 JWT 转发给 MeshX；MeshX 侧只 decode payload。启用 trusted claims 时必须同时设置 `MESHX_AUTH_TRUSTED_JWT_CLAIMS_PROTECTED_LISTENER=true`，表示 MeshX listener 已由 loopback、反向代理、网络策略或等价机制保护。不要把启用 trusted claims 的 MeshX 端口直接暴露给客户端。

### MeshX Automation 本地执行

`AGENTTEAM_DEV_FEATURES=MESHX make dev:server` 默认启用 MeshX server-managed automation runner。用户在 Automation Hub 点击 Start 后，MeshX 创建 run/job ledger，server runner 会自动 claim 与 `MESHX_AUTOMATION_DOCKER_IMAGES` 匹配的 shell-only job，并在 Docker 容器中执行；不需要另起 Desktop runner，也不需要手动运行官方 `actions/runner` 进程。

本地执行需要 Docker daemon 可用，并配置 `MESHX_AUTOMATION_DOCKER_IMAGES`。map 的 key 是 `runs-on` label，value 是包含 `/bin/sh`、并可接受任意 numeric user 的 runner image；生产 Docker runner 只发布这些 key，不使用 `MESHX_AUTOMATION_SERVER_RUNNER_LABELS`。默认网络为 `none`，资源限制为 1 CPU、1 GiB memory、256 pids。`MESHX_AUTOMATION_DOCKER_USER` 为空时，容器以 MeshX 进程的 UID:GID 运行，确保 bind-mounted `/workspace` 可写且 host cleanup 可删除作业产物；显式覆盖为 image user 时，operator 必须保证该用户对 runner workdir 有写权限并且 MeshX 进程可清理其产物。`MESHX_AUTOMATION_DOCKER_MOUNTS` 可选配置 operator-controlled bind mount，格式为 `host_path:container_path[:ro|rw]`，target 不能位于 `/workspace` 下；未配置时不会挂载额外宿主机目录。如果 image map 为空或格式错误，且 `MESHX_AUTOMATION_SERVER_RUNNER_ENABLED=true`，MeshX 会启动失败。如果 Docker daemon 不可连，MeshX 不会回退到宿主机 shell，也不会发布 server runner label；该进程会继续启动，但 server-managed runner 会被禁用，直到修复 Docker 后重启 MeshX。需要只调试官方 runner 兼容层时可把 server runner 关掉。

准备一个包含 shell `run` step、并声明 `manual` 的 AGS automation，例如 `.agentteam/smoke.yml`：

```yaml
name: Smoke
on: manual
jobs:
  smoke:
    runs-on: ubuntu-latest
    steps:
      - uses: agentteam/checkout
      - run: echo hello
```

Server runner 只执行当前 Automation MVP 支持的 shell job：`run` step、用于当前 AGS 仓库和当前已解析 commit SHA 的显式 `uses: agentteam/checkout`、发布 job workdir 内 Markdown 的显式 `uses: agentteam/publish-wiki`、把 job workdir 内文件树提交到 AGS 仓库的显式 `uses: agentteam/publish-tree`，以及 automation/job/step `env` 的安全子集（普通字符串、`${{ inputs.NAME }}`、`${{ secrets.NAME }}`）。除这个 `env` 子集外不做 expressions、container/service/matrix，也不支持任意 repo/ref checkout。AgentTeam-native waits 继续由 wait runtime 处理，不进入 runner queue。默认每个 job 使用 `.data/meshx/automation-runner/<workspace>/<run>/<job-run>/` 独立工作目录，并 bind mount 到容器内 `/workspace`；job timeout 为 30 分钟，stdout/stderr 写入 automation log blob。生产路径不会退回到宿主机 shell 执行；如果不想让 MeshX server 管理本机 Docker 执行，必须禁用 server runner 或提供后续独立 runner backend。生产启用 MeshX Automation 等价于允许受信任 workspace 在 MeshX server 上执行 shell；不要把这个能力开放给不受信任 workspace。

### MeshX 官方 `actions/runner` 调试

官方 runner 兼容层保留为调试和未来自托管 runner 能力；正常本地和产品路径不依赖它。需要验证兼容层时，可以继续使用 smoke 脚本。运行 smoke 前应先用 `MESHX_AUTOMATION_SERVER_RUNNER_ENABLED=false` 启动 MeshX，避免默认 server runner 抢占与 Docker image map 匹配的 smoke job。更多选项见 `scripts/meshx-actions-runner-smoke.sh --help`。

```bash
AGENTTEAM_AUTOMATION_API_URL=http://localhost:3001 \
MESHX_AUTOMATION_ACTIONS_RUNNER_BASE_URL=http://127.0.0.1:3001 \
AGENTTEAM_ACCESS_TOKEN=... \
WORKSPACE_ID=... \
RUNNER_DIR=/path/to/actions-runner \
scripts/meshx-actions-runner-smoke.sh
```

`AGENTTEAM_AUTOMATION_API_URL` 是走 AgentTeam gateway 的认证控制面地址；`MESHX_AUTOMATION_ACTIONS_RUNNER_BASE_URL` 是官方 runner `config.sh --url` 使用的 runner protocol base。下面是官方 runner smoke 脚本展开后的手动流程。

```bash
export AGENTTEAM_AUTOMATION_API_URL=http://localhost:3001
export MESHX_AUTOMATION_ACTIONS_RUNNER_BASE_URL=http://127.0.0.1:3001

REGISTRATION_TOKEN="$(
  curl -sS -X POST "$AGENTTEAM_AUTOMATION_API_URL/api/v1/workspaces/$WORKSPACE_ID/automations/runners/register" \
    -H "Authorization: Bearer $AGENTTEAM_ACCESS_TOKEN" \
    -H "X-Workspace-Id: $WORKSPACE_ID" \
    -H "Content-Type: application/json" \
    -d "{\"runner_id\":\"$RUNNER_NAME\",\"name\":\"$RUNNER_NAME\",\"labels\":[\"ubuntu-latest\"]}" \
  | jq -r .registration_token
)"
```

在已解压的官方 `actions/runner` 目录中配置并启动 runner。兼容层按 GitHub Enterprise Server 形态暴露协议，所以本地必须设置 `GITHUB_ACTIONS_RUNNER_FORCE_GHES=1`：

```bash
GITHUB_ACTIONS_RUNNER_FORCE_GHES=1 ./config.sh \
  --unattended \
  --url "$MESHX_AUTOMATION_ACTIONS_RUNNER_BASE_URL/$OWNER/$REPO" \
  --token "$REGISTRATION_TOKEN" \
  --name "$RUNNER_NAME" \
  --runnergroup Default \
  --labels ubuntu-latest \
  --work _work \
  --replace

./run.sh
```

手动启动 automation run，并查看 run 和 job 状态：

```bash
RUN_ID="$(
  curl -sS -X POST "$AGENTTEAM_AUTOMATION_API_URL/api/v1/workspaces/$WORKSPACE_ID/automations/repositories/$OWNER/$REPO/runs" \
    -H "Authorization: Bearer $AGENTTEAM_ACCESS_TOKEN" \
    -H "X-Workspace-Id: $WORKSPACE_ID" \
    -H "Content-Type: application/json" \
    -d '{"ref":"refs/heads/main","definition_path":".agentteam/smoke.yml"}' \
  | jq -r .id
)"

curl -sS "$AGENTTEAM_AUTOMATION_API_URL/api/v1/workspaces/$WORKSPACE_ID/automations/runs/$RUN_ID" \
  -H "Authorization: Bearer $AGENTTEAM_ACCESS_TOKEN" \
  -H "X-Workspace-Id: $WORKSPACE_ID" | jq .

curl -sS "$AGENTTEAM_AUTOMATION_API_URL/api/v1/workspaces/$WORKSPACE_ID/automations/runs/$RUN_ID/jobs" \
  -H "Authorization: Bearer $AGENTTEAM_ACCESS_TOKEN" \
  -H "X-Workspace-Id: $WORKSPACE_ID" | jq .
```

成功的 shell-only 冒烟应看到 run status `succeeded`、job status `succeeded`，并且 job `output_json` 中保留官方 runner `completejob` 回调提供的 `step_results`、`annotations` 或 `telemetry` 等终态 metadata。手动/API 启动只接受声明了 `manual` 的 automation；`on` 可以是 GitHub Actions 的 string、list 或 map 形态。

当前 MVP 明确限制：

- Server-managed runner 只支持 shell `run` steps、显式 `uses: agentteam/checkout`、显式 `uses: agentteam/publish-wiki`、显式 `uses: agentteam/publish-tree` 和 automation/job/step `env` 安全子集；官方 `actions/runner` 兼容层只分发无 `env` 的 shell `run` jobs，不分发 AgentTeam-native `uses` jobs。
- `agentteam/checkout` 只 materialize 当前 AGS 仓库在当前 automation run 创建时固化的 commit SHA；不支持任意 repo/ref、`.git`、credentials persistence、submodules、LFS、sparse checkout 或 fetch depth。
- 不支持 `actions/checkout`、外部 `uses` actions、自定义 action 依赖解析、matrix、containers、services、`defaults`、`if`、job outputs，或 `env` 安全子集外的表达式求值。
- 不实现 GitHub Results service live log 上传；终态状态和 `completejob` metadata 会持久化到 MeshX job-run ledger。
- AgentTeam-native waits 仍由 MeshX wait/event runtime 处理，不占用官方 runner lease。

### MeshX repo code wiki 生成

Repo code wiki 生成 worker 默认使用 `agentteam-insight` CLI。code2wiki 是 repo 自身能力，不要求 repo 先关联 Project；repo code push / sync 完成后可触发 repo-scoped code2wiki job。worker 先导出源仓库快照，再执行 `agentteam-insight graph` 写入本地 insights 目录，随后执行 `agentteam-insight wiki --input <source> --input <insights-dir>` 生成 wiki Markdown，并把生成页面写入该 repo 自己的 AGS wiki。AgentTeam repo Wiki 页面即可展示。KB 是否能引用这些页面由 KB 的 `project_repo_wiki_ref` 逻辑独立决定。当前本地手动验证可在 repo Wiki 页点击“生成代码 Wiki”，该按钮会创建 `generate_repo_wiki_from_code` job。

Automation Hub 的新建 automation 对话框也提供 `Codebase KB` 预设。该预设会在当前 AGS 仓库下创建 `<repo>.kb` 仓库，先用 `agentteam-insight graph` 把原仓库 Graph Builder artifacts 提交到 `<repo>.kb` 的 `insights/` 目录，再用 `agentteam-insight wiki` 基于源仓库和该 insights 目录生成并发布 `<repo>.kb` 的 wiki。运行前需要给原仓库配置 `AGS_TOKEN` automation secret，并确保 automation runner 容器能访问 `ags_base_url` 指向的 AgentTeam gateway 或 AGS 地址。

`生成 Wiki` 预设用于本地演示已生成 Markdown 页面和 insights 文件树的发布链路，不再 clone Git 仓库，也不需要代理或 Docker bridge 网络。运行前将本地源目录挂载给 Docker runner，例如：

```env
MESHX_AUTOMATION_DOCKER_MOUNTS=~/tidb-wiki:/mnt/agentteam/tidb-wiki:ro,~/tidb.insights:/mnt/agentteam/tidb.insights:ro
```

`生成 Wiki` 会从 `/mnt/agentteam/tidb-wiki` staging Markdown，并通过 `agentteam/publish-wiki` 原生 step 发布到同 owner 下的 `<repo>.kb` 仓库 wiki。wiki 发布 step 在 MeshX server 内通过嵌入式 AGS handler 写 wiki 和 reconcile 索引，不依赖容器内 git、Python、curl 或网络。发布端会把 Markdown 相对路径转换成 AGS 可写且不会形成父子前缀冲突的 wiki slug：根 `README.md` 发布为 `overview`，嵌套 `README.md` 发布为对应目录下的 `overview` 页，非法字符和过长 segment 会被归一化。带 `label` 的发布会在写入前清理同 label 下已经不存在于本次源目录的旧页面。默认每次 wiki 发布最多处理 5000 个 Markdown 页面、10000 个目录项，单页最大 2 MiB；operator 可通过 `MESHX_AUTOMATION_WIKI_PUBLISH_MAX_PAGES`、`MESHX_AUTOMATION_WIKI_PUBLISH_MAX_ENTRIES`、`MESHX_AUTOMATION_WIKI_PUBLISH_MAX_PAGE_BYTES` 调整硬上限，automation YAML 的 `max_pages` 只能进一步收窄 page 上限。

同一个 `生成 Wiki` job 也会从 `/mnt/agentteam/tidb.insights` staging 文件，并通过 `agentteam/publish-tree` 原生 step 直接提交到 `<repo>.kb` 仓库默认分支。tree 发布保留相对文件路径和文件名，不做 wiki slug、wiki page 或 label 处理；发布端会跳过 `.git` 控制目录和 symlink，用一个新的 root tree 替换目标分支内容。当前 tree 发布接受 UTF-8 regular files；遇到其他二进制文件会 fail closed，避免通过 JSON inline content 写坏内容。默认每次 tree 发布最多处理 10000 个文件、20000 个目录项，单文件最大 16 MiB，总内容最大 256 MiB；operator 可通过 `MESHX_AUTOMATION_TREE_PUBLISH_MAX_FILES`、`MESHX_AUTOMATION_TREE_PUBLISH_MAX_ENTRIES`、`MESHX_AUTOMATION_TREE_PUBLISH_MAX_FILE_BYTES`、`MESHX_AUTOMATION_TREE_PUBLISH_MAX_BYTES` 调整硬上限，automation YAML 的 `max_files` 只能进一步收窄文件上限。

`agentteam-insight` 是独立 kit module，源码位于 `kits/agentteam-insight`，本地测试和构建需要在该目录内执行 `go test ./...` 和 `go build .`。

已有 `.agentteam-insight/knowledge-graph.json` 时，可运行 `agentteam-insight domain --input <repo>`，复用 graph 的 OpenCode/LLM 配置执行一次 Understand Anything domain analyzer，并原子生成 `.agentteam-insight/domain-graph.json`。该命令不会读取源代码；缺少 knowledge graph 时应先运行 `agentteam-insight graph`。

```env
MESHX_WIKI_WORK_DIR=.data/meshx/kb-code-wiki
MESHX_WIKI_AGENTTEAM_INSIGHT_BINARY=agentteam-insight
MESHX_WIKI_WORKER_INTERVAL=30s
MESHX_WIKI_WORKER_BATCH=1
MESHX_WIKI_TIMEOUT=30m
MESHX_WIKI_LANGUAGE=zh
MESHX_WIKI_OPENCODE_BINARY=
MESHX_WIKI_OPENCODE_AGENT=
MESHX_WIKI_OPENCODE_MODEL=
MESHX_WIKI_CONCURRENCY=4
```

MeshX 不再在 repo code wiki worker 里调用进程内 DeepWiki SDK。生成链路与 `kits/agentteam-insight` 保持一致：`MESHX_WIKI_CONCURRENCY` 会同时作为 `graph`/`wiki` 的 `--jobs`，并始终附带 `--isolate-opencode-runtime`，避免并发 OpenCode 进程共享全局数据库。`MESHX_WIKI_OPENCODE_MODEL` 会传给 `agentteam-insight graph --model` 和 `agentteam-insight wiki --model`；未配置时会兼容读取旧的 `MESHX_WIKI_LLM_MODEL`。`README.md` 会发布为 `overview` wiki slug，其它 Markdown 文件按相对路径去掉 `.md` 后作为 slug；写入 AGS 前仍会打上 `code-wiki-generated` label，并触发 AGS wiki reconcile。

新的 worktree 可以先执行：

```bash
make ensure-web-deps
```

`make dev:server`、`make regression-fast` 和 `make e2e-web` 也会自动执行这一步。

## 测试

后端常规测试：

```bash
go test -p 2 ./...
```

Desktop Agent Tools focused tests:

```bash
cd desktop
go test -count=1 ./internal/channel/agentteam ./internal/dispatcher ./internal/promptcompiler ./internal/agent
go test -count=1 ./internal/api -run TestRouterAgentTools
go test -count=1 ./internal/cmd -run TestAgentTools
```

仓库里部分后端测试会启动本地 unistore/TiDB runtime。裸 `go test ./...` 会按 Go 默认 package
并发启动多个重型测试包，容易在本机抢 CPU/IO 并拖慢整体耗时；`-p 2` 与 backend required shard 的默认并发保持一致。
API 集成测试的 `StartRuntime*` helper 还会把同一 test binary 内的活跃 runtime 默认限制为 2 个，避免 package 内部
`t.Parallel()` 同时创建过多隔离 TiDB 数据库；需要临时调高时可设置 `AGENTTEAM_API_TEST_RUNTIME_PARALLELISM=N`。
backend required 的 TiDB-heavy shards 默认设置 `AGENTTEAM_TIDB_TEST_TIMING=1`，并输出
`[tidb:testkit:timing]` 记录，用于区分隔离数据库 clone/等待时间和测试体执行时间；本地定位时也可以为单个
`go test -v` 命令设置同一环境变量。

仓库常用快速回归：

```bash
make regression-fast
```

`make regression-fast` 现在会先执行 `scripts/ci-backend-required.sh validate`，确保 root module 的 `go list ./...` 包集合仍被 backend required shards 完整且不重复地覆盖。

如果只需要验证仓库 harness / guardrail 层，可以运行：

```bash
make harness-fast
```

`make harness-fast` 会执行 Electron cutover 自测与 guardrail、repository layout、AGENTS skill 引用、repo-owned skill metadata、代码形态 inventory、测试质量 inventory、静态性能 inventory、前端质量 / first-paint 风险 inventory、API clean-code inventory、MeshX service architecture boundary、observability、store JSON boundary、store refactor boundary、web workspace boundary 等检查。命令会继续运行全部检查，并在末尾统一汇总失败项后返回非零状态，避免前一项失败遮住后续问题。部分检查带有当前债务基线：它们允许现存债务继续可见，但会阻止对应计数继续增加。清理债务后应同步下调 `scripts/harness-fast.sh` 中的基线值。

Daemon protocol E2E：

```bash
go test -tags=e2e ./e2e -count=1
```

Web E2E：

```bash
make e2e-web
cd web && npm run e2e:ci:smoke
```

`make e2e-web` 现在是 `scripts/ci-web-e2e-full.sh` 的 thin wrapper：会补齐 `web/node_modules`，确保 `web/dist` 已构建，然后走 CI 风格的 full suite。Web E2E 的 `web/dist` 由 `scripts/ensure-web-dist.sh` 维护；它用 Vite-only build 生成 preview 产物，并在源码指纹未变化时跳过重复构建。`npm run e2e:ci:smoke` 是更小的 required browser smoke 入口，并固定以 preview frontend + `workers=1` + `retries=2` 跑 `smoke.spec.ts`。这两条路径都复用同一套 `web/e2e/run-web-e2e.mjs` runner，而不是维护第二套 Playwright config。wrapper 和 runner 会向 stderr 打印 `[e2e:timing] key=value ...` 日志，覆盖 dependency/dist preflight、runner execution、wave/lane、backend/frontend ready、Playwright 和 cleanup elapsed。默认情况下，backend 会用 TiUP 管理的 `tidb` 组件起一个本地 `unistore` runtime，并为每个 fresh harness 创建隔离数据库。如果已有可复用 TiDB 实例，设置 `AGENTTEAM_E2E_TIDB_ADMIN_DSN` 可跳过这一步，并让每个 harness 自动创建隔离数据库。`cmd/agentteam-e2e-server` 二进制会按平台、Go 环境和 backend 源码指纹缓存在 `${XDG_CACHE_HOME:-~/.cache}/agentteam/web-e2e-backend-binaries`，one-shot/CI 和 `npm run e2e:up` fresh harness 都会复用；如需自定义位置，可设置 `AGENTTEAM_E2E_BACKEND_BINARY_CACHE_DIR`。涉及 `agentteam daemon` 的用例会按 daemon 源码指纹复用已构建二进制；如需自定义缓存目录，可设置 `AGENTTEAM_E2E_DAEMON_BINARY_CACHE_DIR`。如需只跑某个 CI lane，可显式指定：

```bash
cd web
npm run e2e:ci:plan
npm run e2e:ci:default -- --lane=ui-auth
```

如果 CI 想把 full suite 拆成一个 lane 一个 job，使用 repo-owned 单 lane wrapper：

```bash
make e2e-web-lane lane=ui-auth
cd web
npm run e2e:ci:lane -- ui-auth
```

这个 wrapper 会先用 runner-owned plan 校验 lane，再通过 runner 的 `ci:default --lane <name>` 入口执行现有 lane，不改 worker、retry、隔离数据库或 lane 成员。各 lane 只准备 `web/node_modules` 和 `web/dist`；带 `--list` 时只准备 `web/node_modules`，不会构建 preview 产物。外部 CI 拆 lane job 时应保留 `npm run e2e:ci:plan` 输出的 wave 顺序：先并发 `ui-auth` / `ui-collab`，再并发 `thread-reactions` / `org`，最后跑 `runtime-daemon`。

带 lane scheduler 的 `ci` / `ci:default` 命令只接受 `--lane` 和 `--list` 这类 runner 参数；如果需要原始 Playwright 选择器或 `--shard` 做本地调试，请改用 `npm run e2e -- ...` 或 `npm run e2e:reuse -- ...`。`ci:smoke` 只固定 smoke spec，本身仍可转发原始 Playwright 参数。`AGENTTEAM_E2E_WORKERS=1` 也主要用于这些本地 ad-hoc 路径；CI lanes 自带固定 worker 配置。

完整 Web E2E harness 说明见 `docs/testing/web-e2e.md`。

复用已启动 harness 的本地快速复跑：

```bash
cd web
npm run e2e:up
npm run e2e:reuse -- e2e/auth.spec.ts -g "register, log out, and log back in with a 6-character password"
npm run e2e:down
```

- `e2e:reuse` 在 harness 健康时会跳过后端和前端重启。
- 如果改了后端代码，先执行 `npm run e2e:restart` 再复跑。

Daemon 测试：

```bash
cd desktop
make test
make ci-required
```

`make test` 与 `make ci-required` 都直接验证 desktop Go module，不依赖
frontend build artifact。Web Desktop 与 Electron main/preload 使用上面的
独立命令验证。

本地只构建 daemon/CLI 时，在 `desktop/` 执行 `make build-daemon`。需要
Linux release payload 时使用 `make linux-release` 并显式提供 managed
OpenCode binary/models source；该路径不调用 Node。Electron staging 从
`desktop/release/managed-opencode.env` 读取固定 runtime version，并只使用
目标平台原生 runtime source，不复制用户 PATH 中的 external runtime。

Apple Silicon macOS 上的 `make build-daemon`、`make dev:desktop`、
`make install` 默认使用 `darwin/arm64` 目标，即使当前 `go` 进程本身跑在
Rosetta 下。需要刻意构建其他架构时，显式设置 `GOARCH` 或
`AGENTTEAM_BUILD_GOARCH`。
