LobeHub 本地开发服务器(Local Dev Server)完整操作指南:Agent 验收测试后端的启动、重启与排障
本指南以 LobeHub 仓库中的 dev-server.md 为核心,系统梳理了 agent 验收(acceptance)测试体系下本地开发服务器的启动、重启、端口解析与故障排查全流程。这套服务器是 CLI、Electron、Web 三类测试表面(test surface)共同访问的后端唯一事实源(single source of truth),掌握它你就能在 LobeHub 仓库中稳定拉起"与生产异步执行方式一致"的本地测试环境,并快速定位 ECONNREFUSED、端口占用、队列模式失效等典型问题。
它在验收测试流程中的位置:先理解为何需要这份文档
LobeHub 仓库的 acceptance 体系由三层文档共同定义契约、流程与命令:
- PROCESS.md 拥有"流程"——一次验证运行如何被计划、批准、执行、发布与拆除,其 Step 2(Environment and auth)要求在任何测试之前先解析环境并启动本地服务;
- PROJECT.md 拥有"命令"——端口、服务、表面(surface)与探针的 LobeHub 专属入口;
- dev-server.md 则被定位为"开始/重启后端的单一事实源",所有测试表面(CLI、Electron、Web)最终访问的都是这套服务器。
因此文档开篇就强调两条铁律:
- 先解析端口再启动:在启动或探测任何本地测试表面之前,先按 PROCESS.md 的 Step 2 运行
test-env.sh解析环境; - 不要硬编码端口表:端口从环境解析器读取,而不是从任何静态表格猜。
从源码看,.agents/acceptance/scripts/test-env.sh 正是这套"环境解析器":它刻意设计为只读,按 .env → .env.$NODE_ENV → .env.local → .env.$NODE_ENV.local 的顺序加载文件,再叠加 shell 环境变量作为最高优先级,最终打印 APP_URL、PORT、SERVER_URL、AUTH_TRUSTED_ORIGINS、SPA_PORT、MOBILE_SPA_PORT、DESKTOP_PORT 七个键的值及其来源(文件/shell/自动分配/推断)。
三种运行模式与各自端口来源
| 命令 | 运行内容 | 端口来源 |
|---|---|---|
pnpm run dev:next |
Next.js 后端(API + auth) | PORT |
bun run dev |
全栈(Next.js + Vite SPA,经由 devStartupSequence) |
PORT + SPA_PORT |
bun run dev:spa |
仅 Vite SPA,API 代理到 PORT |
SPA_PORT |
三种模式对应三种测试诉求:
pnpm run dev:next:只跑 Next.js 后端(next dev -p 3010,见 package.json 中的dev:next脚本)。适合 CLI 表面与后端接口验证,但不会提供 SPA 页面;bun run dev:全栈模式,是 Web smoke 测试的硬性前提。它执行根脚本dev(tsx scripts/devStartupSequence.mts),由编排器同时拉起 Next 与 Vite,且 Next 会把 SPA HTML 从 Vite 代理过来。从 scripts/devStartupSequence.mts 源码可见其端口决策逻辑:Next 端口按-p CLI 参数 > PORT 环境变量 > 从 3010 起探测第一个空闲端口的优先级解析;Vite 端口在非移动端取SPA_PORT(无显式值时从 9876 起探测空闲端口),并把结果同时写入SPA_PORT与VITE_DEV_PORT两个环境变量——后者是 Next 侧读取 Vite 服务位置的唯一契约,不依赖任何端口文件;bun run dev:spa:仅 Vite SPA,将 API 请求代理到PORT。PROJECT.md 还提醒:需要 Next 代理 SPA HTML 时不能只用dev-next,而dev:spa在"本地前端 + 生产后端"场景下会打印一个_dangerous_local_dev_proxyURL,只用于验证前端对生产数据的行为,不可用于测试后端分支改动。
cloud 仓库工作树到默认端口的映射
文档特别说明:当本仓库作为 lobehub/ 子模块存在于 cloud 仓库时,只要 .env 与 shell 环境都未提供取值,工作树名称才会映射到下面的兜底默认值:
| 工作区目录 | 默认 SERVER_URL |
|---|---|
lobehub |
http://localhost:3010 |
lobehub-cloud |
http://localhost:3020 |
lobehub-cloud-1 |
http://localhost:3021 |
lobehub-cloud-N |
http://localhost:$((3020 + N)) |
从 init-dev-env.sh 与 setup-auth.sh 的源码可以印证这一映射的实现:两者在解析工作区根目录时,若当前目录名为 lobehub 且其父目录名匹配 lobehub-cloud*,就会把 WORKSPACE_ROOT 上提为 cloud 父目录,并据此计算偏移量(default_port 函数把 3020 作为 cloud 的基端口,普通 lobehub 目录回落 3010)。
test-env.sh 与 setup-auth.sh 都会优先使用已解析的环境变量,上述工作树默认值只是最后兜底。因此文档给出了测试时的关键纪律:当使用非标准端口时,以 dev 服务器的终端输出为最终事实源,并把解析结果导出给每一条 agent-testing 命令:
export SERVER_URL=http://localhost:<port-from-dev-output>
端口与环境的解析:两种启动路径的分水岭
dev-server.md 把启动路径按"是否存在仓库根 .env"一分为二,这与 PROCESS.md Step 2 和 PROJECT.md §2 保持一致:
- 存在根
.env:一律使用现有本地配置,直接pnpm run dev:next/bun run dev,不要调用任何init-dev-env.sh子命令; - 不存在
.env:使用自包含的 agent-testing 环境,由 .agents/acceptance/scripts/init-dev-env.sh 完成整套引导。
init-dev-env.sh 是理解后文所有命令的关键:它自带守卫——一旦仓库根目录存在 .env,除帮助外的所有子命令都会立即退出,从机制上保证"绝不覆盖用户已有配置"。它的完整子命令一览(对应源码注释区与 case 分发):
| 子命令 | 作用 |
|---|---|
env |
打印可 source 的 shell 导出 |
write [file] |
写出可 source 的 env 文件(默认 .records/env/agent-testing-dev.env) |
setup-db |
启动本地 Postgres(paradedb 容器 lobehub-agent-testing-postgres)与 Redis(lobehub-agent-testing-redis)并跑迁移 |
migrate |
对已配置数据库执行 bun run db:migrate |
seed-user |
植入基线测试用户 + CLI API key,并写出 .records/env/agent-testing-cli.env |
qstash |
在独立终端运行本地 Upstash QStash dev 服务(默认 127.0.0.1:8080) |
s3 |
运行本地 s3rver 对象存储(默认 29000 端口) |
preflight |
检查 agent-runtime 前置条件(QStash + S3 + Redis + dev server) |
dev-next |
以该环境 exec pnpm exec next dev -p $SERVER_PORT |
dev |
以该环境 exec bun run dev |
stop-dev/clean |
停止由本脚本启动的 dev 进程树(保留 DB/Redis/S3 数据) |
clean-db/clean-s3 |
移除受管容器 / 本地 S3 测试数据 |
在无 .env 模式下,脚本会通过 _load_or_alloc_ports 自动分配不与已有实例冲突的 SERVER_PORT/SPA_PORT(从 20000+ 随机探测、首次分配后持久化到 .records/env/agent-testing-ports.env),并把 AGENT_RUNTIME_MODE 默认设为 queue。注意其 dev-next 实现(源码 cmd_dev_next)有一个易被忽略的细节:直接 pnpm exec next dev -p "$SERVER_PORT" 而非走 dev:next 脚本,是因为子模块的 dev:next 硬编码了 -p 3010,当端口被自动分配为非 3010 时会绑定错误端口。
健康检查:探测前先确认服务器在线
无论哪种模式,探测(probe)前的标准健康检查都是一条 curl:
curl -s -o /dev/null -w '%{http_code}' "$SERVER_URL/"
只要端口上有 HTTP 监听者即视为就绪——脚本源码中 _http_reachable 的判定正是"任何非 000 的 HTTP 状态码即算可达"。这套判定在 setup-auth.sh 的 check_server 里也被复用(接受 2xx/3xx)。
启动与重启:标准操作清单
文档给出的启动/重启命令矩阵如下,可直接复制执行:
# 只启动后端。
# 有根 .env:使用现有本地配置。
# Agent 运行时队列模式是必需的,用于镜像生产环境的异步执行。
AGENT_RUNTIME_MODE=queue pnpm run dev:next
# 无根 .env:使用自包含的 agent-testing 环境。
.agents/acceptance/scripts/init-dev-env.sh dev-next
# 全栈 SPA + 后端。Web smoke 测试必需。
# 有根 .env:
AGENT_RUNTIME_MODE=queue bun run dev
# 无根 .env:
.agents/acceptance/scripts/init-dev-env.sh dev
# 本地 QStash。仅在测试 workflow 路径时于独立终端运行。
.agents/acceptance/scripts/init-dev-env.sh qstash
# 重启——拉取服务器端代码改动所必需。
# 对由 init-dev-env.sh 启动的无 .env 服务器,只停它拥有的进程树:
.agents/acceptance/scripts/init-dev-env.sh stop-dev
.agents/acceptance/scripts/init-dev-env.sh dev-next
为什么 AGENT_RUNTIME_MODE=queue 如此重要
文档要求队列模式"以镜像生产异步执行",这背后有明确的工程原因(在 PROJECT.md §6 中被标注为硬性约束):queue 模式(本仓库与生产环境的默认值)下,创建 agent 操作会先向本地 QStash(127.0.0.1:8080)POST 投递;一旦 QStash 未运行,agent 运行会在任何 LLM 调用之前死于 ECONNREFUSED 127.0.0.1:8080 / fetch failed——没有 trace 被记录,且错误读起来与测试环境无关。init-dev-env.sh 默认就设置 AGENT_RUNTIME_MODE=queue(见 apply_env),并用 _qstash_reachable 这类"以 QStash REST 契约为准"的探针(/v2/schedules 无 token 应 401、带配置 token 应 200)来识别真正的 QStash 监听者,避免 8080 端口被其他服务占位时误判。
因此官方推荐的完整无 .env 引导序列是(见 PROJECT.md §2):
.agents/acceptance/scripts/init-dev-env.sh setup-db
.agents/acceptance/scripts/init-dev-env.sh s3 # 终端 B,保持运行
.agents/acceptance/scripts/init-dev-env.sh seed-user
.agents/acceptance/scripts/init-dev-env.sh qstash # 终端 B,保持运行
.agents/acceptance/scripts/init-dev-env.sh dev
停止时的进程所有权纪律
stop-dev/clean 不是粗暴的进程名匹配。从源码 cmd_stop_dev 看,它读取 write_dev_state 持久化的状态文件(.records/runtime/agent-testing-dev.state,记录 PID、进程启动时间、cwd 与模式),经 state_owns_process 三项核验(进程存在、启动时间一致、cwd 属于仓库根)后,再对整棵后代进程树发 SIGTERM,超时才 SIGKILL。这正是 PROCESS.md Step 6 所要求的"只停本轮启动的东西"在脚本层的落地。配套测试 init-dev-env.test.sh 验证了这些行为:重复启动不会替换活跃的所有权记录、clean A 不会误杀同级 B、篡改过 PROCESS_START 的陈旧状态会被拒绝清理。
什么时候必须重启服务器
Next.js 热更新可能无法捕获 workspace 包内的改动——文档的判定原则是"存疑即重启"。以下是官方的重启矩阵:
| 改动位置 | 需要重启? |
|---|---|
apps/server/src/(routers、services、modules) |
是 |
apps/server/src/router-hono/ |
是 |
packages/database/(模型) |
是 |
packages/types/ |
是 |
packages/prompts/ |
是 |
apps/cli/(CLI 从源码直接运行) |
否 |
其中 apps/server 位于根 pnpm workspace 之内(pnpm-workspace.yaml 声明的 workspace 列表),而 apps/cli 需要独立安装且直接以 bun src/index.ts 从源码运行(无构建步骤),所以 CLI 侧代码改动即时生效、无需重启。仓库根 package.json 的 dev:next 等脚本与 PROJECT.md §1 中"根 workspace 不覆盖 apps/desktop 与 apps/cli"的说明可互为印证——测试前必须在涉及的独立 app 内单独 pnpm install,否则会出现 workspace 包解析失败。
故障排查速查表
| 问题 | 解决方案 |
|---|---|
ECONNREFUSED |
服务器未运行——启动它 |
端口上的 EADDRINUSE |
检查监听者;仅当该进程是本轮自己启动的才停掉它。绝不只凭端口杀掉未知 PID |
| 数据陈旧 / 行为仍是旧的 | 服务器需要重启以拾取代码改动(对照上文重启矩阵) |
| Agent 调用以内联方式执行 | 设置 AGENT_RUNTIME_MODE=queue,确认 REDIS_URL 已配置,然后重启服务器 |
| 队列模式需要 Redis | 运行 init-dev-env.sh setup-db,或为已有 Redis 提供 REDIS_URL=redis://... |
| QStash workflow 失败 | 启动 init-dev-env.sh qstash,并确保 dev 服务器继承了脚本的 QSTASH_* 环境变量 |
这些条目同样能在脚本源码中找到依据:
- 无
.env模式下,DATABASE_URL默认指向本地 5433 端口 Postgres、REDIS_URL指向 6380 端口 Redis(容器lobehub-agent-testing-redis),因此队列模式的运行时状态存储天然需要setup-db提供的 Redis; cmd_qstash通过pnpm run qstash(即pnpx @upstash/qstash-cli@latest dev)拉起本地 QStash,并在apply_env中统一设置QSTASH_URL、QSTASH_TOKEN、QSTASH_CURRENT_SIGNING_KEY、QSTASH_NEXT_SIGNING_KEY——只要服务器由dev/dev-next子命令以exec继承该环境启动,它就能拿到这些QSTASH_*变量;EADDRINUSE与所有权纪律在_pick_free_port(首次分配避开占用端口)与stop_owned_process_tree(只杀自有进程树)中均有体现。
边界:Marketplace 端点不在本地认证闸门内
文档在末尾特意划出一条范围边界:marketplace/community 端点不属于本地 agent-testing 认证闸门的一部分。除非改动明确针对 marketplace 行为,否则不要把本地产品链路验证阻塞在 marketplace API 认证上。这条边界在 PROCESS.md 与 PROJECT.md §6 中也被重复强调("Marketplace/community endpoints are not part of the local auth gate"),说明它是一条贯穿验收流程、需要测试 Agent 始终遵守的环境约束。
小结
LobeHub 的本地 dev server 是整套 agent 验收体系的地基:以 test-env.sh 解析端口、以"有无根 .env"划分两条启动路径、以 AGENT_RUNTIME_MODE=queue 对齐生产异步语义、以进程所有权状态文件保证可安全停启,并以重启矩阵与故障表兜底日常排障。建议将本文与 PROJECT.md、PROCESS.md 配合阅读:前者给你命令,后者给你流程,而 dev-server.md 在它们之间充当启动/重启后端的"单一事实源"。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0630
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00