# 豆包智能服务本地调试指南

`dbx dev` 用于在本机启动豆包智能服务 Web 模拟器。默认在 dbx 项目目录下执行，不要求进入智能服务前端目录。命令会自己解析前端目录、Manifest 和运行态 Skill；默认使用根目录 `manifest.yaml` 与 `skill/SKILL.md`；MCP endpoint 启动时显式传 `--mcp-endpoint <mcp_endpoint>`。

项目存在 `business-templates.yaml` 时，先读 [业务模板调试边界](../business-template-debug.md)。模拟器链路只在创建新的 Sandbox Session 时把 `business-templates.yaml` 与 Manifest 加入 Session 创建请求；本地修改模板或刷新 Web 模拟器不会自动更新已有 Session。Session 创建也不能代替模板单独同步、完整制品上传或真机调试包上传。

`dbx dev` 将解析后的 Manifest 所在目录作为业务模板的默认目录。该目录存在 `business-templates.yaml` 时，CLI 会在启动调试服务前检查文件非空、解析 YAML，并调用兼容模式校验；任一步失败都会停止启动，避免创建一个静默省略或携带非法业务模板的 Sandbox Session。文件不存在是合法状态，表示本次本地调试不配置业务模板。

需要验证 Skill、MCP、Manifest、tool result 和 `card_delta` 的后端链路时，改读 [simulator-eval.md](simulator-eval.md)。

## 先根据 AppID 选择配置路径

开始本地调试前，Agent 必须先读取根目录 `manifest.yaml` 的 `app_key`，并与前端 `runtimeConfig.appId`、业务 Server 实际读取的 AppID 交叉核对。三处应表示同一个 App；不要只凭项目名称或历史配置判断。

按当前 AppID 的格式选择且只选择一条配置路径：

- `doubao-sandbox-app-*`：读取 [sandbox-app.md](sandbox-app.md)，完成沙箱 App 的获取与设置。
- 有效的 `db_app_*`：读取 [real-app.md](real-app.md)，完成真实 App 的本地调试设置。
- AppID 缺失、三处不一致、格式无法识别，或仍是 `db_app_xxxxxx`、`db_app_mock_app`、`db_app_api_test_*` 等占位/示例值：不要猜测 App 类型或混用两套配置，先向开发者确认本轮使用的 App，并把配置统一后再继续。

## 设置本地 Server 的 OpenAPI 调用域名

完成对应 App 配置后，通过环境变量启动业务 Server：

```bash
DOUBAO_OPENAPI_BASE_DOMAIN="https://api-sandbox.doubao-dev.com"
```

`dbx dev` 模拟器生成的登录码、手机号授权码、支付订单和回调状态等调试数据位于沙箱 OpenAPI 环境。业务 Server 必须调用该域名，才能访问同一环境中的模拟数据；如果调用正式 OpenAPI 域名，这些调试数据可能无法识别，导致联调链路失败。

如果当前代码把平台 OpenAPI 域名硬编码在各接口中，先让相关调用统一读取 `DOUBAO_OPENAPI_BASE_DOMAIN`。该变量只影响业务 Server 进程运行时，不写入 `runtimeConfig.apiBaseUrl`、Manifest 或版本配置，也不参与构建、上传或发布。

`runtimeConfig.apiBaseUrl` 指向前端调用的业务 Server，`manifest.mcp_server.end_point` 和 `dbx dev --mcp-endpoint` 指向 MCP Server；这些地址都不是平台 OpenAPI 域名。

## 根据 App 类型处理登录证书

如果本轮登录会使用 `phone_code` 获取并解密手机号，证书处理方式也由 AppID 分流：

- 沙箱 App：按 [sandbox-app.md](sandbox-app.md#在调试器中上传-csr) 准备 CSR，并在 Web 调试器中上传。
- 真实 App：按 [real-app.md](real-app.md#使用平台配置的智能服务应用证书) 使用该 App 在平台配置的「智能服务应用证书」。

两种方式不能混用。业务 Server 必须加载与本轮证书公钥配套的私钥，否则无法解密平台返回的手机号。

## 默认启动

在项目目录下直接运行：

```bash
dbx dev --mcp-endpoint <mcp_endpoint>
```

默认不要传路径参数；只有默认解析失败或项目结构非标准时，才补 `--manifest`、`--skill`。
确实需要传路径参数时，优先使用绝对路径；相对路径会按当前目录解析。
如果使用本地 MCP Server，Agent 应按下文“保持本地 MCP Server 持续运行”选择当前操作系统可用的进程托管方式，同时记录会话或进程名称、状态文件和日志文件。托管的 Server 必须与 `dbx dev` 前台进程解耦；Agent 应能在不中断 Server 的情况下运行调试命令、抓取日志和处理重启，用户不需要另外打开终端或介入启动—检查—重启循环。

本地调试会话中存在连接平台与本机业务 Server 的 Bridge。登录过程中，平台发起的 auth callback 请求（例如 `FetchTokenURL`、`RefreshTokenURL`）可以经 Bridge 调用运行在本地的接口，因此为了联调登录不必先把 callback 服务部署到公网；但本地 Server 必须持续运行，且 Manifest 中配置的 callback 地址、端口和路径必须与实际监听地址一致。

启动业务 Server 时设置上文的 `DOUBAO_OPENAPI_BASE_DOMAIN` 环境变量。再确认 `runtimeConfig.apiBaseUrl`、`mcp_server.end_point` 与即将启动的业务 Server 相符，然后启动 Server。

默认约定：

- 项目目录固定为当前目录，前端目录按项目布局自动解析。
- 当前目录就是 `.dbx` 状态、默认 Manifest、默认运行态 Skill 和前端目录所在目录；默认路径是 `manifest.yaml` 与 `skill/SKILL.md`。
- 前端目录优先读取 `.dbx/config.json` 里的 `frontend.directory`；未配置时按 CLI 默认规则处理。
- Manifest 默认是 `<project>/manifest.yaml`。
- 运行态 Skill 默认是 `<project>/skill/SKILL.md`。
- MCP endpoint 用 `--mcp-endpoint <mcp_endpoint>` 显式传入；本地 Web 模拟器使用 Streamable HTTP `/mcp` 地址。
- `dbx dev` 在 TTY 中以前台 Ink REPL 运行；无 TTY 时以前台模式持续运行 dev service，并在启动后打印 Web Simulator 地址。纯前端改动不要再次执行；按 Ctrl-C 或发送 SIGTERM 即可停止。

需要覆盖默认路径时才加路径参数：

```bash
dbx dev \
  --mcp-endpoint <mcp_endpoint> \
  --manifest <manifest_path> \
  --skill <skill_path_or_dir>
```

`--manifest`、`--skill` 的相对路径都按当前目录解析。`--skill` 可以传 `SKILL.md` 文件；如果传 Skill 目录，CLI 会使用该目录下的 `SKILL.md`。

## 保持本地 MCP Server 持续运行

本地 MCP Server 必须在启动它的单次命令、Agent 工具调用或终端退出后继续运行，并与前台的 `dbx dev` 分开管理。Agent 先判断当前运行环境，再选择进程托管方式：

- macOS、Linux、WSL 或其它已安装 `tmux` 的类 Unix 环境：使用项目专属的 tmux 会话。
- 原生 Windows PowerShell / CMD：使用 PowerShell `Start-Process` 或满足同等约束的本机后台进程命令。
- Windows 使用 WSL 时，让 MCP Server 和 `dbx dev` 都运行在同一个 WSL 环境，再按 tmux 流程处理；不要混用 Windows 路径和 WSL 路径。

两种方式都遵守以下规则：

- 使用唯一且可识别的名称，例如 `dbx-mcp-<project>-<port>`；只管理当前项目对应的会话或进程，不影响用户的其它进程。
- 使用 Server 项目的真实前台启动命令。使用 tmux 或 `Start-Process` 托管时，不要在内部再次后台化；如果当前环境已有更合适的进程管理器，可以把它作为唯一托管层，但仍要满足本节的状态、日志和清理约束。
- 把日志和进程状态放在工程外的系统临时目录并使用绝对路径，避免误提交；状态文件不得包含 AppSecret。
- 将 `DOUBAO_OPENAPI_BASE_DOMAIN` 传入 Server 进程环境。
- 启动前检查同名会话或状态：进程健康且目录、端口、命令、OpenAPI BaseDomain 和 endpoint 配置一致时复用；配置不一致或 endpoint 不健康时再重启。

### macOS、Linux 和 WSL：使用 tmux

先确认 `tmux` 可用：

```bash
command -v tmux
```

使用项目专属会话启动 Server。下面的命令由 Agent 执行；`<start_mcp_server_command>` 必须替换成 Server 项目的真实前台启动命令：

```bash
session_name='dbx-mcp-<project>-<port>'
server_dir='<absolute_server_dir>'
log_dir="${TMPDIR:-/tmp}/dbx-mcp"
log_file="${log_dir}/${session_name}.log"

mkdir -p "$log_dir"
: > "$log_file"

tmux new-session -d \
  -s "$session_name" \
  -c "$server_dir" \
  -e 'DOUBAO_OPENAPI_BASE_DOMAIN=https://api-sandbox.doubao-dev.com' \
  -e 'MCP_PORT=<port>' \
  "<start_mcp_server_command> 2>&1 | tee -a '$log_file'"
```

`tee` 同时把 stdout/stderr 保留在 tmux pane 并追加到日志文件；Server 进程和 `tee` 结束后 pane 随之退出，便于把“会话消失”识别为进程异常退出。

检查状态和最近日志：

```bash
tmux has-session -t "$session_name"
tmux capture-pane -p -t "${session_name}:0.0" -S -200
tail -n 200 "$log_file"
```

持续观察日志时运行：

```bash
tail -F "$log_file"
```

只让日志观察命令持续到当前问题定位结束，再退出 `tail -F`；不要把它误认为 Server 进程。只有用户明确想自行观察 pane 时，才补充 `tmux attach -t <session_name>`，使用 `Ctrl-b d` 脱离。

需要重启时，先结束当前项目的会话，再用新的环境变量重新执行启动命令：

```bash
tmux kill-session -t "$session_name"
```

调试结束且用户没有要求保留 Server 时，也结束该会话并确认 endpoint 已停止。

### 原生 Windows：使用 PowerShell 后台进程

在原生 Windows 中使用 `Start-Process` 启动独立后台进程，通过 PID 文件和 stdout/stderr 日志管理。不要使用 `Start-Job`，因为 PowerShell Job 通常依赖创建它的 PowerShell 会话。

Agent 先根据项目的真实启动命令拆出可执行文件和参数。例如 pnpm 脚本通常使用 `pnpm.cmd` 和 `@("start")`，直接运行 Node 入口时使用 `node.exe` 和入口文件参数。不要把 AppSecret 放入参数数组。

下面的命令由 Agent 在一次独立的 PowerShell 调用中执行；启动进程继承环境后，该 PowerShell 调用即可退出：

```powershell
$name = "dbx-mcp-<project>-<port>"
$serverDir = "<absolute_server_dir>"
$port = <port>
$serverExecutable = "<executable_or_cmd>"
$serverArguments = @("<arg1>", "<arg2>")
$stateDir = Join-Path $env:TEMP "dbx-mcp"
$pidFile = Join-Path $stateDir "$name.pid"
$stdoutLog = Join-Path $stateDir "$name.stdout.log"
$stderrLog = Join-Path $stateDir "$name.stderr.log"

New-Item -ItemType Directory -Force -Path $stateDir | Out-Null
$env:DOUBAO_OPENAPI_BASE_DOMAIN = "https://api-sandbox.doubao-dev.com"
$env:MCP_PORT = "$port"

$process = Start-Process `
  -FilePath $serverExecutable `
  -ArgumentList $serverArguments `
  -WorkingDirectory $serverDir `
  -RedirectStandardOutput $stdoutLog `
  -RedirectStandardError $stderrLog `
  -WindowStyle Hidden `
  -PassThru

Set-Content -LiteralPath $pidFile -Encoding ASCII -Value $process.Id
```

PID 文件不写敏感配置。Agent 执行前先读取已有 PID；进程仍存活且目录、端口、命令、OpenAPI BaseDomain 和 endpoint 都一致时复用，不要重复启动。

检查进程状态和最近日志：

```powershell
$name='dbx-mcp-<project>-<port>'; $stateDir=Join-Path $env:TEMP 'dbx-mcp'; $pid=[int](Get-Content -LiteralPath (Join-Path $stateDir "$name.pid")); Get-Process -Id $pid -ErrorAction SilentlyContinue
$name='dbx-mcp-<project>-<port>'; $stateDir=Join-Path $env:TEMP 'dbx-mcp'; Get-Content -LiteralPath @((Join-Path $stateDir "$name.stdout.log"),(Join-Path $stateDir "$name.stderr.log")) -Tail 200
```

持续观察日志时，按当前问题选择 stdout 或 stderr；需要同时观察时由 Agent 分别管理两个查看命令：

```powershell
$name='dbx-mcp-<project>-<port>'; $stateDir=Join-Path $env:TEMP 'dbx-mcp'; Get-Content -LiteralPath (Join-Path $stateDir "$name.stdout.log") -Tail 200 -Wait
$name='dbx-mcp-<project>-<port>'; $stateDir=Join-Path $env:TEMP 'dbx-mcp'; Get-Content -LiteralPath (Join-Path $stateDir "$name.stderr.log") -Tail 200 -Wait
```

按 `Ctrl-C` 只结束日志观察命令，不会停止 MCP Server。代码、凭据、目录、端口或启动命令变化时，先结束旧进程，再用新的环境和参数重新执行启动命令：

```powershell
$name='dbx-mcp-<project>-<port>'; $stateDir=Join-Path $env:TEMP 'dbx-mcp'; $pid=[int](Get-Content -LiteralPath (Join-Path $stateDir "$name.pid")); taskkill.exe /PID $pid /T /F
```

如果当前环境有更合适的 Windows 进程托管方式，Agent 可以调整具体命令，但必须保留独立后台运行、子进程环境注入、PID 或等价状态、日志重定向、状态检查和只清理当前项目进程这些能力。调试结束且用户没有要求保留 Server 时，结束该进程并确认 endpoint 已停止。

### 逃生出口：让用户手动前台启动

如果 Agent 无法可靠创建或管理后台进程，给用户一条可直接复制的单行命令，让用户在自己的终端前台启动 MCP Server。只把它作为逃生出口：提前说明该终端必须保持打开，关闭窗口或按 `Ctrl-C` 会停止 Server；Agent 仍负责随后验证 endpoint 和继续运行 `dbx dev`。

生成用户熟悉的“进入目录、设置 OpenAPI BaseDomain、运行项目命令”单行命令。把目录和环境变量值放在引号中，保持为一个物理行，不要使用 Bash 反斜杠、PowerShell 反引号或参数数组。Agent 发送前必须替换 Server 目录、端口和真实启动命令，直接给用户一条可复制执行且没有占位符的命令。

macOS、Linux 或 WSL：

```bash
cd "<absolute_server_dir>" && DOUBAO_OPENAPI_BASE_DOMAIN="https://api-sandbox.doubao-dev.com" MCP_PORT="<port>" <start_mcp_server_command>
```

原生 Windows PowerShell：

```powershell
cd "<absolute_server_dir>"; $env:DOUBAO_OPENAPI_BASE_DOMAIN="https://api-sandbox.doubao-dev.com"; $env:MCP_PORT="<port>"; <start_mcp_server_command>
```

例如 pnpm 项目把 `<start_mcp_server_command>` 替换为 `pnpm start`。如果启动命令中的路径也包含空格，按当前 Shell 的常规方式给该路径加引号。

`tmux has-session` 或 PID 状态只证明托管进程仍存在，不证明 MCP 协议已经就绪；日志查看也不代替 endpoint 验证。无论使用哪种操作系统分支，启动或重启后都必须继续验证 MCP `initialize`、`notifications/initialized` 和 `tools/list`。

回复用户时给出 MCP endpoint、托管方式、会话或进程名称、日志路径和协议验证结果。

## 调试环境检查

启动 Web 模拟器前先确认本机调试环境，避免把路径、Manifest 或 MCP 进程问题误判成前端渲染问题：

- 在 dbx 项目目录运行；这里的项目目录是整个 dbx 项目根，不是智能服务前端目录。
- `.dbx/config.json` 的 `frontend.directory` 指向真实前端目录；未配置时确认默认前端目录存在且能被 kit 识别。
- 项目的 `<project>/manifest.yaml` 存在，且通过后端 `/yaml/verify` 校验；除非本次传 `--mcp-endpoint`，否则 Manifest 里必须有可用的 `mcp_server.end_point`。
- 项目的 `<project>/skill/SKILL.md` 存在；传 `--skill` 目录时目录下也必须有 `SKILL.md`。
- 如果 MCP Server 是本地进程，由 Agent 按当前操作系统创建或复用项目专属的 tmux 会话或 Windows 后台进程，确认托管状态正常、启动日志没有报错，再确认 `/mcp` endpoint 从当前机器可访问。
- 确认 `DOUBAO_OPENAPI_BASE_DOMAIN` 已传入业务 Server 进程。
- 如果本轮涉及登录、`FetchTokenURL` 或 `RefreshTokenURL`，确认业务 Server 将兑换 code、MCP access/refresh token、过期/撤销/轮换状态存入 SQLite 或其它持久化数据库，不使用进程内内存作为唯一存储。
- 如果本轮涉及手机号解密，按当前 App 类型完成证书检查：沙箱 App 确认 Web 调试器已输入 `dev.csr` 内容；真实 App 确认平台已为该 App 配置「智能服务应用证书」。两种路径都要确认业务 Server 使用配对私钥。
- 确认业务 Server 日志能看到工具调用、登录接口和下游 OpenAPI 的 request/trace ID、耗时、结果码及平台 `log_id`，且没有打印完整 code/token、手机号、AppSecret 或请求体。
- MCP endpoint 必须从当前机器可访问；Web 模拟器本地 MCP 代理优先使用标准 Streamable HTTP `/mcp` 地址。
- `dbx dev` 成功后，保留完整 `web_debugger` URL 和 `logs` 路径；启动、编译、端口、路径问题看 `logs`，模拟器交互问题看 Agent Trace。

## MCP Endpoint

`dbx dev` 启动时推荐显式传 `--mcp-endpoint <mcp_endpoint>`，不要依赖默认解析。该地址可以直接使用 Manifest 中的 `mcp_server.end_point`。

- 本地 MCP Server 使用实际监听的 `http://127.0.0.1:<port>/mcp`。
- `DOUBAO_SIMULATOR_MCP_ENDPOINT` 环境变量也可以覆盖 Manifest，优先级低于命令行参数。
- Web 调试器会在完整 URL 中追加 `mcp=/__mcp_proxy`，浏览器侧请求先到本地 Web 调试器，再由开发服务代理到该 endpoint。

`dbx dev` 接入 MCP 时遵守这些协议约束：

- `dbx dev --mcp-endpoint` 面向 kit Web 模拟器的本地 MCP 客户端，使用标准 MCP Streamable HTTP transport；endpoint 应指向 `/mcp`，不要传 SSE `/sse`。
- MCP Server 必须支持标准 JSON-RPC MCP 流程：`initialize`、`notifications/initialized`、`tools/list`，以及模型触发工具后执行的 `tools/call`。
- `tools/list` 暴露的工具名要和 `manifest.tools` 对齐；需要出卡的工具必须同时存在于 MCP `tools/list`、`manifest.tools` 和对应 entity 的 `tool_card_binding`。
- `tools/call` 返回标准 MCP ToolResult。智能服务出卡结果建议放在 `structuredContent.entities`；每个 entity 的 `entity_type` 必须匹配 `manifest.entities`，并包含 Manifest / Widget 约定的实体 ID 字段。
- 工具业务失败时返回 `isError: true`，并提供可读的 `content` 或 `structuredContent.message`；Web 模拟器会按失败工具调用展示。
- 不要依赖非标准 `_meta` 字段做 app 绑定；app、Manifest、Skill 绑定以调试 URL 和 Manifest 校验结果为准。

Server 验证通过后再运行：

```bash
dbx dev --mcp-endpoint http://127.0.0.1:<port>/mcp
```

## 返回调试地址

`dbx dev` 成功后会输出 `web_debugger`。回复用户时必须给完整地址：

```text
web_debugger: http://127.0.0.1:<port>/?simulator=...&simulatorManifest=...&simulatorSkill=...&mcp=...
```

保留 query 很重要：`simulator`、`simulatorManifest`、`simulatorSkill`、`mcp` 等参数决定 Web 模拟器加载哪个本地调试代理、Manifest、Skill 和 MCP 代理。若命令自动打开了浏览器，也仍然把完整 `web_debugger` URL 和 `logs` 路径返回给用户。

## 查询 Web 模拟器日志

kit 提供的 Web 模拟器日志主要在页面里的 Agent Trace 面板。

- 打开完整 `web_debugger` URL 后，在页面 Agent Trace 中查看 simulator 请求、Manifest/Skill 加载、MCP tools、tool call、tool report、card 和 runtime 事件。
- 如果需要从命令行取当前页面快照，用 `web_debugger` 的 origin 请求 `/__agent_trace`：

```bash
curl -s 'http://127.0.0.1:<web_debugger_port>/__agent_trace'
```

常用精简查询示例：

```bash
curl -s 'http://127.0.0.1:<web_debugger_port>/__agent_trace' \
  | jq '.snapshot.simulator | {
      stream_status,
      messages,
      tool_calls,
      trace_events,
      error
    }'
```

`/__agent_trace` 只保存当前 Web 调试页面最近一次自动上报的快照。页面没打开或还没完成上报时会返回 `{"stored":false}`；刷新页面或发送一次模拟器 query 后再查。返回结构重点看：

- `snapshot.simulator.messages`
- `snapshot.simulator.tool_calls`
- `snapshot.simulator.trace_events`
- `snapshot.widget_runtime_trace_events`

`dbx dev` 的终端输出适合查启动失败、编译失败、端口和路径问题；模拟器交互链路优先看 Agent Trace 或 `/__agent_trace`。

## 刷新与停止

- 前端 Page / Widget / 样式：等热更新后刷新 Web 模拟器，或点“刷新资源并重载卡片”；不要重启工程。
- `skill/SKILL.md`：通常刷新 Web 模拟器即可重新读取。
- `manifest.yaml`：只修改 tools、entities、card binding、名称或描述时，刷新 Web 模拟器可更新本地资源；如果本轮还使用 Sandbox 平台服务，必须再在调试面板点击“撤回并重建 session”，让平台 Session 使用同一份新 Manifest。
- `business-templates.yaml`：`dbx dev` 只在启动预检时自动校验；启动后修改文件，调试面板的“撤回并重建 session”不会重新运行 CLI 校验。先手动运行文件校验，再重建 Session；只刷新页面或卡片资源不会更新已有 Session。
- 修改 Manifest `app_key`、`mcp_server.end_point`、Manifest 路径或 PPE 环境时，停止并重新执行 `dbx dev`；只刷新资源或重建 Session 不能更新 CLI/Kit 启动时解析的 AppID、MCP endpoint 和进程环境。
- 路径、Manifest/Skill 入口、MCP endpoint/端口变化后，才重新执行 `dbx dev`。
- MCP Server 代码变化且启动脚本不支持热更新时，由 Agent 按当前操作系统对应的托管方式重启 Server，检查启动日志并重新验证 MCP 协议；不要让用户代为重启。
- 调试结束后由 Agent 停止 `dbx dev`。如果用户没有要求保留本地 Server，也结束当前项目的 tmux 会话或 Windows 后台进程并确认 endpoint 已停止；日志文件可以保留到本轮问题定位结束。

## 常见问题

- 如果本地 MCP endpoint 不可访问，Agent 先按当前操作系统检查 tmux 会话或 Windows PID 状态、最近日志和实际监听端口，修复后自行重启并复验；不要把排查转交给用户，也不要改用 `dbx localdebug daemon` 掩盖进程生命周期问题。
- 如果手机号接口成功但业务 Server 无法解密，先按 AppID 回到对应配置文件：沙箱 App 检查 Web 调试器中的 CSR 与 Server 私钥是否配对；真实 App 检查平台「智能服务应用证书」与 Server 私钥是否配对。
- 如果 Server 重启后平台携带 refresh token 被拒绝，检查 `RefreshTokenURL` 是否仍在查询 SQLite/持久化 token 表；不要用内存 `Map`、对象或数组保存 token 状态。应重启业务 Server 后复测一次刷新流程。
- 如果 Agent Trace 里出现 `local_mcp_tools_load_failed` 或 `local_mcp_call_failed`，按 [Simulator Eval 指南](simulator-eval.md) 检查 MCP Server 的实现是否正确。
