首页
/ qwen serve 快速上手与运维全指南:从启动、验证到内部调用链

qwen serve 快速上手与运维全指南:从启动、验证到内部调用链

2026-09-14 23:59:25作者:翟萌耘Ralph

本篇技术指南以 Qwen Code 的 daemon 模式(qwen serve)为主题,完整覆盖如何启动本地 HTTP 服务、如何用 curl 验证其工作状态、以及从 qwen serve 命令行到监听端口的内部调用链。读完本文,你将掌握十余种面向不同场景的启动配方、全部启动参数与环境变量的语义、boot 拒绝(refusal)的显式失败场景、以及优雅/强制关闭与嵌入边界等运维要点。

1. 最短路径:一条命令启动 daemon

在任意工作目录下执行:

qwen serve

预期输出(端口、workspace 以实际环境为准):

qwen serve listening on http://127.0.0.1:4170 (mode=http-bridge, workspace=/your/cwd)
qwen serve: bound to workspace "/your/cwd"
qwen serve: trusted loopback mode; local callers have full API access without bearer authentication, including code execution as the daemon user. Use --require-auth with QWEN_SERVER_TOKEN on shared or untrusted hosts.

在浏览器中打开 http://127.0.0.1:4170/ 即可获得 Web Shell UI(聊天、会话列表与 workspace 检查)。默认的无 token 回环(loopback)主监听器赋予本地调用者完整的操作员 API 权限,同时保留 workspace 信任、会话归属、client-id、权限、feature、validation 与 resource 等全部检查。

createServeApp() 将打包好的 Web Shell 静态资源(packages/cli/src/serve/web-shell-static.ts)挂载在 bearerAuth 之前(见 server.ts 中的注释与实现),因此 shell 本身无需 token 即可加载;当配置了鉴权时,shell 自身的 API 调用会携带 bearer——启动时加 --open(token 放在 URL fragment 中,永远不会发送到服务端),或启用鉴权后手动追加 #token=…--no-web 则完全退出 Web 界面,只保留 API。

2. 启动配方:13 种典型场景

# 1. 本地开发默认(loopback,无 token)
qwen serve

# 2. 显式指定 workspace + 临时端口
qwen serve --workspace /path/to/repo --port 0

# 3. 加固的 loopback 开发(即便在 loopback 上也强制 bearer)
QWEN_SERVER_TOKEN=$(openssl rand -hex 32) qwen serve --require-auth

# 4. 暴露到局域网(建议使用稳定 token;不提供 token 时
#    daemon 会生成一次性 bearer 并打印一次)
QWEN_SERVER_TOKEN=$(openssl rand -hex 32) \
  qwen serve --hostname 0.0.0.0 --port 4170

# 4b. 暴露到局域网,使用自动生成的一次性 bearer
qwen serve --hostname 0.0.0.0 --port 4170

# 5. 面向大量会话与更大的 replay ring 调优
qwen serve --max-sessions 0 --event-ring-size 32000

# 6. 多客户端协作 + 严格的 MCP 预算
QWEN_SERVER_TOKEN=secret \
  qwen serve --require-auth \
             --mcp-client-budget 10 \
             --mcp-budget-mode enforce

# 7. 使用 settings.json 中配置的 consensus 策略启动
# settings.json: { "policy": { "permissionStrategy": "consensus", "consensusQuorum": 2 } }
qwen serve

# 8. 调试日志
QWEN_SERVE_DEBUG=1 qwen serve

# 9. 禁用 F2 池(回退到按会话的 MCP 客户端)
QWEN_SERVE_NO_MCP_POOL=1 qwen serve

# 10. 允许浏览器 Web UI 跨域访问
QWEN_SERVER_TOKEN=secret \
  qwen serve --allow-origin 'http://localhost:3000'

# 11. Prompt 截止时间 + SSE 空闲超时
qwen serve --prompt-deadline-ms 300000 --writer-idle-timeout-ms 600000

# 12. 空闲 ACP 子进程在工作排空后至少保留 60s 可复用
qwen serve --channel-idle-timeout-ms 60000

# 13. 开启 HTTP 限流
QWEN_SERVE_RATE_LIMIT=1 qwen serve

关于配方 3 的特别说明:在加固的 loopback 模式下,/health 注册在 bearerAuth 之后,因此健康检查探针必须像其他 API 路由一样携带 token(Web Shell 静态面保持 pre-auth 是设计使然;如需纯 API daemon 请加 --no-web)。

3. 全部启动参数(Full Startup Flags)

CLI 定义于 packages/cli/src/commands/serve.ts(yargs builder 中逐项声明,handler 中完成 boot 前置校验)。以下为完整参数表:

Flag 类型 默认值 何时必填 作用
--port <n> number 4170 - TCP 端口;0 表示由操作系统分配临时端口。
--hostname <host> string 127.0.0.1 非 loopback 绑定在空 token 源、或 localhost 解析到非 loopback 且无 token 源时拒绝启动 绑定地址。loopback 值包括任意 127.0.0.0/8 地址、localhost::1[::1]localhost 只解析一次并固定,trusted 模式会在启动完成前校验实际监听地址。[::1] 的方括号会自动剥离;host:port 输入会被拒绝并提示改用 --port。非 loopback 结果必定携带 bearer:若 --tokenQWEN_SERVER_TOKEN 都未提供,boot 会生成一个临时 bearer 而非拒绝。例外:生成与否取决于拼写——解析到非 loopback 的 localhost 永不生成,只有在解析出 token 源时才启动;而解析到 loopback 的非字面主机名会生成并打印其 bearer token。
--token <s> string env / 无 非 loopback 拼写无需有效源(会自动生成,但空值会拒绝);在 loopback 上:--require-auth--allow-origin '*' 或非 loopback HTTP(S) 的 --allow-origin 时需要 Bearer token;启动时裁剪一次。显式空值是“已提供”的源,因此会遮蔽 QWEN_SERVER_TOKEN 并解析为无 token——非 loopback 绑定随即拒绝启动,解析到非 loopback 且无其他 token 源的 localhost 绑定同样拒绝(对它永不生成)。它会出现在 /proc/<pid>/cmdline 中,因此优先使用 QWEN_SERVER_TOKEN。boot 的 stderr 也会对此发出警告(源码见 serve.ts)。
--max-sessions <n> number 32 - 每 workspace 的活跃会话上限。超出后新的 spawn 请求返回 503。0 表示无限制。NaN/负数会抛出异常。
--max-total-sessions <n> number 800,或容量 ≤ 25 时推导 - 全 daemon 的活跃会话上限。省略时:注册容量 > 25(含默认 256)时为 800(即使只有一个 workspace);容量 ≤ 25 时从每 workspace 上限与启动/恢复的 workspace 数量推导一次,其中一个 workspace 无限制。动态注册不会重算。0 表示无限制。
--memory-budget-mb <n> integer,[1024, 1048576] cgroup/主机内存的 50% - daemon 进程树的总内存预算,以解析出的可用内存为上限。不对任何子进程做尺寸分配;当前唯一消费者是自适应 live-journal 增长池(见 --max-journal-bytes)。在 limits.memory 中报告,含建模的每子进程分区。
--max-journal-events <n> 正 safe integer 10000 - 每会话 in-flight liveJournal replay 条目的基线上限。自适应增长可提升该值(见 --max-journal-bytes);固定任一 journal 参数会禁用增长。
--max-journal-bytes <n> 正 safe integer 8388608 - 每会话 in-flight liveJournal 的基线字节上限。超限的 turn 会按需增长 cap(向翻倍方向,受剩余池头寸限制),池为有效 --memory-budget-mb 的 5%(上限 1024 MB;当有效预算低于 1024 MB 下限时为 0,即禁用增长),绝不越过每会话 256 MiB 硬上限;固定任一 journal 参数会禁用增长。
--memory-pressure-mode <mode> off | observe observe 仅观测模式 两种模式下都报告 runtime.memory.pressure;只有 observe 会触发 daemon_memory_pressure issue。仅限根进程。
--child-heap-mode <mode> off | observe observe 仅观测模式 observe 下在 limits.memory.childHeap 报告建模分区;不应用、不拒绝任何东西。off 下该块的 two figures 为 null
--max-pending-prompts-per-session <n> number 5 - 每会话已接受但待处理/运行中的 prompt 上限。超出返回 503。0/Infinity 表示无限制。负数或非整数抛异常。
--workspace <dir> string / 可重复 process.cwd() - 启动 workspace 运行时;重复使用可注册额外的隔离运行时,第一个为 primary。每个值必须是绝对路径、必须存在、必须是目录。boot 通过 canonicalizeWorkspace 规范化每个值。cwd 不匹配的 POST /session 返回 400 workspace_mismatch
--max-connections <n> number 256 - 监听器级别 server.maxConnections0/Infinity 无限制。NaN/负数导致 boot 失败,避免 fail-open。
--require-auth boolean false loopback 上需要 token 源 将 bearer 鉴权扩展到 loopback 以及 /health。无 token 源时 boot 拒绝启动——这是仅限 loopback 的 fail-fast,因为非 loopback 绑定上生成的临时 token 满足该标志。loopback 源包括 --tokenQWEN_SERVER_TOKEN--open-with-auth(后者在 boot 前安装自己生成的 token,因此 --require-auth --open-with-auth 可以启动)。
--enable-session-shell boolean false bearer 或 trusted loopback 启用直接 POST /session/:id/shell 执行。调用方还必须发送会话绑定的 X-Qwen-Client-Id
--event-ring-size <n> number 8000 - 每会话 SSE replay ring 深度。软上限 MAX_EVENT_RING_SIZE = 1_000_000;越界值在 bridge 构造时抛出。默认值与 bridge 共用同一常量 DEFAULT_RING_SIZE(见 serve.ts)。
--http-bridge boolean true - Bridge 模式:生产环境尝试预热一个 primary qwen --acp 子进程,首次使用失败后重试;trusted 次级按需启动一个,untrusted 次级无法启动 ACP。Stage 2 进程内模式尚未实现;--no-http-bridge 会回退并打印到 stderr。
--mcp-client-budget <n> number mcp-budget-mode=enforce 时必填 workspace MCP 客户端上限。必须是正整数。
--mcp-budget-mode <m> 'enforce' | 'warn' | 'off' 设置预算时为 warn,否则 off enforce 需要 --mcp-client-budget enforce 拒绝超额连接(按 mcpServers 声明顺序确定性判定,disabledReason: "budget");warn 在 75% 时仅警告;off 纯观测。
--allow-origin <pattern> 可重复 string - 替换默认 Origin 拒绝策略的 CORS 白名单。通配符与非 loopback HTTP(S) origin 需要已解析的 token;这些守卫在生成之后读取 token,因此非 loopback 绑定上生成的临时 bearer 即可满足,拒绝仅限 loopback。
--allow-private-auth-base-url boolean false - 允许安装 localhost/私有网络 auth provider 的 baseUrl。仅限受信任的本地开发使用。
--prompt-deadline-ms <n> number - 服务端 prompt 墙钟限制(ms);超时中止 prompt。
--writer-idle-timeout-ms <n> number - 每 SSE 连接的空闲超时(ms)。
--channel-idle-timeout-ms <n> 非负整数 0 - 运行时工作排空后 ACP 子进程的自动回收延迟。保留普通预热供首次使用。活跃 keepalive 窗口可能延长配置的延迟,取剩余更长的延迟。
--initialize-timeout-ms <n> number 10000 - ACP 子进程启动截止时间(channel factory + initialize 握手)及默认请求超时(ms)。
--session-reap-interval-ms <n> number 60000 - 会话 reaper 扫描间隔。0 禁用。
--session-idle-timeout-ms <n> number 1800000 - 断连会话的空闲超时。0 禁用。
--rate-limit / --no-rate-limit boolean env / off - 启用/禁用按层级 HTTP 限流。
--rate-limit-prompt <n> number 10 --rate-limit 窗口内 prompt 请求数。
--rate-limit-mutation <n> number 30 --rate-limit 窗口内 mutation 请求数。
--rate-limit-read <n> number 120 --rate-limit 窗口内读请求数。
--rate-limit-window-ms <n> number 60000 --rate-limit 限流窗口长度;必须 >= 1000

handler 中,--mcp-client-budget 必须为正整数、--mcp-budget-mode=enforce 必须携带预算,否则直接 process.exit(1)(见 serve.ts);--memory-budget-mb 通过 isValidMemoryBudgetMb 校验;设置 --rate-limit 后各 tier 参数会先回退读取 QWEN_SERVE_RATE_LIMIT_* 环境变量再校验正整数(serve.ts)。

4. 环境变量

Env 等价 flag / 作用
QWEN_SERVER_TOKEN 等价于 --token--token 优先。boot 时裁剪一次,避免 cat token.txt 的尾部换行。两个源都未设置时,非 loopback 绑定会生成临时 bearer;daemon 从不自行设置该变量。已提供但为空的源绝不“缺席”,因此总是抑制生成——但空值只在单方向上决定解析出的 token:空 --token 遮蔽此处设置的值并解析为无 token(非 loopback 绑定随即拒绝),而此处的空值仅在未传 --token 时解析为无 token——非空 --token 仍然胜出,daemon 基于它启动。解析到非 loopback 的 localhost 绑定同样永不生成,只在解析出 token 源时启动(见 --hostname)。
QWEN_SERVE_DEBUG 1/true/on/yes(不区分大小写)启用详细 stderr 日志。
QWEN_SERVE_NO_MCP_POOL 1 完全禁用 workspace MCP 池,回退到按会话的 McpClientManager。Capabilities 停止通告 mcp_workspace_pool/mcp_pool_restart
QWEN_SERVE_MCP_CLIENT_BUDGET ACP 子进程内部预算输入。CLI 通过 childEnvOverrides--mcp-client-budget 生成;不是父进程 env 回退。
QWEN_SERVE_MCP_BUDGET_MODE ACP 子进程内部预算模式。CLI 通过 childEnvOverrides--mcp-budget-mode 生成;不是父进程 env 回退。
QWEN_SERVE_PROMPT_DEADLINE_MS --prompt-deadline-ms 的 env 回退。
QWEN_SERVE_WRITER_IDLE_TIMEOUT_MS --writer-idle-timeout-ms 的 env 回退。
QWEN_SERVE_MCP_POOL_TRANSPORTS 由 ACP 子进程读取。逗号分隔的池化传输白名单;默认 stdio,websocket
QWEN_SERVE_MCP_POOL_DRAIN_MS 由 ACP 子进程读取。池条目空闲排空延迟;默认 30000,限制在 1000..600000 ms。
QWEN_SERVE_RATE_LIMIT 1/true 启用限流;CLI flag 优先。
QWEN_SERVE_RATE_LIMIT_PROMPT --rate-limit-prompt 的 env 回退。
QWEN_SERVE_RATE_LIMIT_MUTATION --rate-limit-mutation 的 env 回退。
QWEN_SERVE_RATE_LIMIT_READ --rate-limit-read 的 env 回退。
QWEN_SERVE_RATE_LIMIT_WINDOW_MS --rate-limit-window-ms 的 env 回退。

按 handle 的 env 覆盖是有意为之:同一进程内运行两个 daemon 不会在 process.env 上竞争。defaultSpawnChannelFactory 在 spawn 时快照 env。

5. settings.json 同样会被读取

Boot 会调用一次 loadSettings(boundWorkspace)

Key 类型 行为
policy.permissionStrategy 'first-responder' | 'designated' | 'consensus' | 'local-only' 设置 BridgeOptions.permissionPolicyBoot 用 validatePolicyConfig 校验;未知值抛出 InvalidPolicyConfigError 而非静默回退。
policy.consensusQuorum 正整数 consensus 策略的 N。默认 floor(M/2)+1。在非 consensus 策略下设置会被忽略,boot 在 stderr 打印警告。
context.fileName string 控制 POST /workspace/init 通过 workspace-service 的 contextFilename 写入哪个文件。
tools.disabled string[] 通过 normalizeDisabledToolList() 规范化(trim、丢弃空项、去重)后影响下一个 ACP 子进程 spawn。
tools.approvalMode string 默认会话审批模式。
telemetry object OTel 配置:enabledotlpEndpointotlpProtocol、各信号端点等。详见 17-configuration.md

settings 的 I/O 失败(如 JSON 损坏)回退到默认值。InvalidPolicyConfigError 是例外:策略配置错误会显式导致 boot 失败。

6. Boot 拒绝场景(显式失败)

run-qwen-serve.ts 在以下场景有意抛出而非回退

需要先厘清:非 loopback 绑定且完全没有 token 源不是拒绝——daemon 会生成一个临时的 128-bit bearer(16 随机字节、22 个 base64url 字符),启动时打印一次,每次重启都会轮换(详见 qwen-serve.md 的 remote quickstart)。Loopback 拼写从不生成。--open-with-auth 的生成器是独立的且更大——32 字节、256-bit bearer——且只在 loopback 上运行。

所有 token 守卫读取的都是解析后的 token,即生成步骤之后的值。这正是 --require-auth--allow-origin 的拒绝仅限 loopback 的原因:非 loopback 绑定上已有生成的 bearer 就位。

场景 错误前缀
非 loopback 绑定且显式空 token 源(--token '' / QWEN_SERVER_TOKEN='' Refusing to bind ... without a bearer token
loopback 绑定且无 token 源时设置 --require-auth--tokenQWEN_SERVER_TOKEN--open-with-auth Refusing to start with --require-auth set but no bearer token
--workspace 不存在、不是目录或不是绝对路径 Invalid --workspace ...
--workspace stat 权限被拒 Invalid --workspace ...: permission denied
--mcp-client-budget 不是正整数 Must be a positive integer
--mcp-budget-mode=enforce 无预算 requires a positive mcpClientBudget
--hostname 写成了 localhost:4170 looks like a "host:port" combination. Use --port
--hostname [::1]:8080 Invalid --hostname ... brackets indicate an IPv6 literal but the value is not a clean [addr] form
--max-connectionsNaN 或负数 Must be >= 0
--event-ring-size > 1_000_000 bridge 构造时抛出
无 token 的 loopback 绑定上 --allow-origin '*' Refusing to start with --allow-origin '*' but no bearer token configured
无 token 的 loopback 绑定上非 loopback HTTP(S) --allow-origin Refusing to start with --allow-origin ... but no bearer token configured
--prompt-deadline-ms / --writer-idle-timeout-ms 不是正整数 Must be a positive integer
--initialize-timeout-ms 不是正整数或超过 2^31-1 Must be a positive integer / Exceeds maximum JS timer delay
未知 policy.permissionStrategy 或非正 policy.consensusQuorum InvalidPolicyConfigError

7. curl 验证清单

# 1. 存活检查
curl http://127.0.0.1:4170/health
# -> {"status":"ok"}

# 1.1 深度健康检查
curl -s 'http://127.0.0.1:4170/health?deep=1' | jq

# 2. 能力列表
curl -s http://127.0.0.1:4170/capabilities | jq

# 3. 预检就绪状态
curl -s http://127.0.0.1:4170/workspace/preflight | jq

# 4. 环境快照(密钥只报告是否存在)
curl -s http://127.0.0.1:4170/workspace/env | jq

# 5. MCP 池 / 预算快照
curl -s http://127.0.0.1:4170/workspace/mcp | jq

# 6. 创建会话
curl -s -X POST http://127.0.0.1:4170/session \
  -H 'Content-Type: application/json' \
  -H 'X-Qwen-Client-Id: curl-debug' \
  -d '{}' | jq

# 7. 订阅 SSE(将 <sid> 替换为真实会话 id)
curl -N \
  -H 'Accept: text/event-stream' \
  -H 'X-Qwen-Client-Id: curl-debug' \
  -H 'Last-Event-ID: 0' \
  'http://127.0.0.1:4170/session/<sid>/events'

# 8. Web Shell UI
open http://127.0.0.1:4170/

启用 bearer 鉴权时,每个请求都要加上 -H "Authorization: Bearer $QWEN_SERVER_TOKEN"。在生成 token 的流程中该变量永远不会被设置——daemon 在启动时打印一次 bearer 且不导出它——因此需要先把打印的值粘贴到自己的 shell 中:

TOKEN='<printed bearer>'
curl -s -H "Authorization: Bearer $TOKEN" http://<bind>:4170/capabilities | jq

8. 有浏览器 UI 吗?—— Web Shell 详解

有——就是 Web Shell。 resolveWebShellDir() 定位构建产物(发布时打包在 CLI bundle 旁,checkout 时位于 packages/web-shell/dist),mountWebShellAssets()//assets/session/:id 文档导航上提供它们(浏览器深链——直接 curl /session/<id> 拿到的是 API 的 401/404,而不是 shell)。当产物缺失时 daemon 降级为仅 API 而不是崩溃;--no-web 显式退出。

静态 shell 在所有启动模式下都挂载在 bearerAuth 之前——浏览器无法在地址栏导航或 <script src> 子资源上附加 Authorization 头,对它们上锁只会破坏 UI。API 调用在无 token 的 primary 监听器上使用 trusted-loopback 权威,或在鉴权模式下附加配置的 bearer。在带 token 的非 loopback 绑定上,shell 的同源 HTTP 请求无需白名单:Origin 等于直接 socket 协议加规范化 Host 权威的请求会被 bearer 鉴权,然后在 CORS 墙前剥离,因此它的 POST 可以通行。仍有三种情况需要显式 --allow-origin <origin>:terminal 与 voice 功能背后的 WebSocket 升级;浏览器通过 TLS 终结代理访问 daemon 时(其 https origin 永远匹配不上明文 socket);以及任何重写 Host 头的纯 HTTP 中间件(nginx 默认的 proxy_set_header Host $proxy_host、k8s Ingress),其转发的 Host 不再匹配浏览器的 Origin。转发头从不被信任用于建立同源,因此解决方式是白名单条目或原样转发 Host 的代理;仅端口翻译(docker -p 8080:4170)在非 loopback 绑定上无需任何配置,因为只比较转发的 Host——在默认的 loopback 绑定上,Host 门禁会对翻译后的端口返回 403 Invalid Host header(包括 shell 文档在内的每个请求),且 --allow-origin 无法覆盖它,所以要么转发同一端口(ssh -L 4170:localhost:4170),要么绑定非 loopback。

CSP 由 buildWebShellCsp() 构建,刻意比静态页面更宽松(内联 performance.measure 补丁需要 'unsafe-inline'、shiki 与 mermaid 需要 eval/wasm/blob workers、katex 字体需要 data:、SSE 需要 connect-src 'self')。frame-ancestors 'none'X-Frame-Options: DENY 阻止点击劫持,除非通过 --allow-origin 显式允许某个扩展 origin,以便 UI 可托管在 Chrome 侧边栏(issue #5626)。

如需原始协议检查,可直接订阅 SSE 流(routes/sse-events.ts)——参见第 7 节的 curl 配方。

9. 从 qwen serve 到监听端口的内部调用链

qwen serve
   |
   v (process)
packages/cli/index.ts              main()
   |
   v
llm.tsx                         main() - parseArguments()
   |
   v (yargs assembly)
config/config.ts                   import { serveCommand } ...
config/config.ts                   .command(serveCommand)
config/config.ts                   await yargsInstance.parse()
   |
   v (handler)
commands/serve.ts                  handler(argv) - boot pre-checks
commands/serve.ts                  const { runQwenServe } = await import('../serve/index.js')   # lazy load
commands/serve.ts                  await runQwenServe({...})
   |
   v
serve/run-qwen-serve.ts              runQwenServe(opts, deps)
   |  |- resolve token (trim / env fallback / non-loopback ephemeral generation)
   |  |- hostname mismatch fallback
   |  |- auth preflight (reads the resolved token)
   |  |- workspace validation + canonicalization
   |  |- MCP budget validation + childEnvOverrides
   |  |- loadSettings + validatePolicyConfig
   |  |- PermissionAuditRing + publisher
   |  |- resolveBridgeFsFactory
   |  `- createHttpAcpBridge({...})
   |
   v
serve/run-qwen-serve.ts              const app = createServeApp(opts, () => actualPort, {...})
   |
   v
serve/server.ts                    createServeApp() - builds Express app (**does not listen**)
   |  |- middleware chain (loopback Origin strip / access log + trace id / Host allowlist / remote same-origin strip / CORS / pre-auth health + shell / webhooks / bearerAuth / rate limit / JSON / telemetry / per-route mutation gate)
   |  |- route mounting (health / web-shell static / capabilities / workspace / session / SSE / ACP HTTP)
   |  `- return app
   |
   v
serve/run-qwen-serve.ts              server = createServer(app) / https.createServer(..., app)
   |  |- lifecycle.bindServer(server, { startupReady, drainHost })
   |  |- server.listen(port, hostname)
   |  |- server.maxConnections = cap
   |  |- actualPort = server.address().port
   |  |- write "qwen serve listening on ..."
   |  |- register SIGINT / SIGTERM (onSignal)
   |  `- resolve(handle: RunHandle)
   |
   v
commands/serve.ts                  await blockForever()    // block forever until signal

关键事实:

  • createServeApp 只构建,不监听。 它返回一个挂载了中间件与路由的 express() 实例。仅普通嵌入者可以继续自己 app.listen()。使用 Live/Conversations 的嵌入者必须在监听前把真实 Node server 绑定到导出的 app lifecycle,并在关闭时 await 该 lifecycle。
  • () => actualPort 是惰性闭包。 actualPortserver.listen 回调中赋值。hostAllowlist 中间件按需读取它,因此临时端口(--port 0)仍能正确门禁 Host 头。
  • await blockForever() 是有意为之。 如果 yargs.parse() 解析完成,CLI 顶层会落入交互式 TUI 入口(llm.tsx)。SIGINT/SIGTERM 通过 runQwenServeonSignal 路径退出。该函数实现于 serve.ts,是一个永不 resolve 的 Promise。

10. HTTP 路由文件拆分

主装配发生在 server.tscreateServeApp() 中,它串联中间件并挂载聚焦的路由模块:

路由 文件 挂载入口
/health packages/cli/src/serve/routes/health.ts healthRoutes.register()
/daemon/status packages/cli/src/serve/routes/daemon-status.ts registerDaemonStatusRoutes()
/capabilities、workspace init/tool/MCP mutation 路由、ACP HTTP bridge packages/cli/src/serve/server.ts createServeApp() 内直接注册
Workspace status、env、preflight、MCP/tool/provider/skill 摘要 packages/cli/src/serve/routes/workspace-status.ts registerWorkspaceStatusRoutes()registerWorkspaceDiagnosticStatusRoutes()
Workspace 扩展与扩展操作 packages/cli/src/serve/routes/workspace-extensions.ts registerWorkspaceExtensionRoutes()
/workspace/memory(GET/POST) packages/cli/src/serve/workspace-memory.ts mountWorkspaceMemoryRoutes()
所有 /workspace/agents CRUD 路由 packages/cli/src/serve/workspace-agents.ts mountWorkspaceAgentsRoutes()
GET /file/file/bytes/list/glob/stat packages/cli/src/serve/routes/workspace-file-read.ts registerWorkspaceFileReadRoutes()
POST /file/write/file/edit packages/cli/src/serve/routes/workspace-file-write.ts registerWorkspaceFileWriteRoutes()
Workspace setup、trust、settings、permissions、voice 路由 packages/cli/src/serve/routes/workspace-*.ts registerWorkspaceSetupGithubRoutes()registerWorkspaceTrustRoutes()
Workspace auth provider 与 device-flow 路由 packages/cli/src/serve/routes/workspace-auth.ts registerWorkspaceAuthRoutes()
Session lifecycle、prompt、metadata、language、shell、recap、rewind、branch、list 路由 packages/cli/src/serve/routes/session.ts registerSessionRoutes()
GET /session/:id/events SSE 流 packages/cli/src/serve/routes/sse-events.ts registerSseEventsRoutes()
Permission 响应路由 packages/cli/src/serve/routes/permission.ts registerPermissionRoutes()

完整的路由与线协议参考见 qwen-serve-protocol.md;架构详见 01-architecture.md

11. 优雅关闭 vs 强制关闭

  • 第一次 SIGINT / SIGTERM -> runQwenServeonSignal -> 两阶段优雅关闭:
    1. bridge.shutdown():每个 channel 先给 KILL_HARD_DEADLINE_MS(10 秒),然后 channel.kill()
    2. server.close():in-flight 请求排空,SHUTDOWN_FORCE_CLOSE_MS(5 秒,见 run-qwen-serve.ts)触发 closeAllConnections(),随后再应用 2 秒的第二截止时间。
  • 关闭过程中再次 SIGINT / SIGTERM -> bridge.killAllSync() 同步 SIGKILL 所有 ACP 子进程并调用 process.exit(1),避免产生孤儿进程。

runQwenServe 返回的 RunHandle.close() 是仓库内部宿主与测试使用的编程等价物。

12. 嵌入边界(Embedding Boundary)

runQwenServecreateServeApp 及其 lifecycle 辅助函数是内部实现 API;发布的 @qwen-code/qwen-code不导出 ./serve 子路径。外部集成应启动 qwen serve --no-web 并使用文档化的 HTTP/SSE 协议或 @qwen-code/sdk。仓库代码与测试可以直接导入源码模块,但这些导入不是受支持的集成契约。

对于仓库内部调用 createServeApp 的场景,默认 fsFactory.trusted = false。Agent 侧的 ACP writeTextFile 会被以 untrusted_workspace 拒绝,并打印一次 stderr 警告。要么注入带显式信任的 deps.fsFactory,要么注入 deps.bridge,要么接受 trust-gated 的默认行为。

13. 调试配方

常见命令(详见 19-observability.md 的调试章节):

# daemon 是否存活?
curl http://127.0.0.1:4170/health

# 通告了哪些能力?
curl -s http://127.0.0.1:4170/capabilities | jq

# Daemon 宿主就绪状态
curl -s http://127.0.0.1:4170/workspace/preflight | jq

# 订阅实时 SSE
curl -N -H 'Accept: text/event-stream' \
     -H 'Last-Event-ID: 0' \
     'http://127.0.0.1:4170/session/<sid>/events'

# 详细日志
QWEN_SERVE_DEBUG=1 qwen serve

参考

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
34
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.21 K
2.81 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
948
1.86 K
docsdocs
暂无描述
Markdown
906
5.84 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
537
610
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
864
1.36 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
4.28 K
1.04 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.39 K
1.48 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
550
401
flutter_flutterflutter_flutter
本仓库是 Flutter SDK 与 Flutter Engine 的 OpenHarmony 适配版本,由 CPF-Flutter 团队维护。开发者可使用熟悉的 Flutter 技术栈开发 OpenHarmony 应用,3.35.7 及以后的适配版本可基于本仓库源码构建支持 OpenHarmony 的 Flutter Engine。
Dart
1.19 K
348