关于 internal-dev-workbench
internal-dev-workbench 是面向 Workflow SDK 贡献者的可选开发工具,可启动三窗格 tmux 会话。它为不同 worktree 配置独立的 portless URL,并提供 Claude Code 状态栏脚本显示服务链接和会话命令。
主要功能
- 01三窗格 tmux 开发会话
- 02按 worktree 区分的 portless 路由
- 03查看当前 workbench 的可观测性界面
- 04Claude Code 状态栏脚本
- 05端到端测试与重启指引
适用场景
- 01并行运行开发服务和可观测性界面
- 02在多个 git worktree 中协作且避免端口冲突
- 03查看服务 URL 并连接 tmux 会话
- 04针对本地会话运行端到端测试
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ vercel/ workflow --skill internal-dev-workbench安装后在 Claude Code 中输入/internal-dev-workbench确认已加载。
附带文件statusline.sh
详细介绍
internal-dev-workbench
为端到端 Workflow SDK 开发启动一个遵循特定约定的 3 窗格 tmux 会话。每个窗格都通过 portless 启动,因此 URL 稳定且限定于各自的 worktree(例如 https://<branch>.turbopack.localhost),让多个 worktree 可以并行运行而不会发生端口冲突。配套的状态栏脚本会在 Claude Code 的提示符中显示当前 URL。
这是供贡献者选择使用的工具。仓库的标准开发流程(在 workbench 中运行 pnpm dev,不使用 portless)不受影响。
先决条件
- 已安装
tmux - 已全局安装
portless(使用npm i -g portless或通过 Homebrew 安装)。运行portless --version进行验证。 - 已初始化仓库:
pnpm install && pnpm build。在全新的 worktree 中首次运行时,必须先完成这两步,才能启动任何开发服务器(workbench 应用依赖已构建的工作区包;若未运行pnpm build,会出现MODULE_NOT_FOUND,无法找到workflow)。 - 使用开发服务器运行端到端测试时,必须设置
WORKFLOW_PUBLIC_MANIFEST=1(否则会拦截/.well-known/workflow/v1/manifest.json)。
布局
使用 main-vertical 时,开发服务器占据左侧栏;右侧栏则在上方显示可观测性 UI,下方显示临时终端:
+----------------------+--------------------------+
| | PANE_OBS: workflow web |
| | (observability UI |
| PANE_DEV: turbopack | scoped to the |
| (Next.js dev) | workbench app) |
| +--------------------------+
| | PANE_SH: zsh scratchpad |
| | (repo root, for build, |
| | tests, e2e, git, etc.) |
+----------------------+--------------------------+
设置
会话名称必须与 worktree 的 portless 前缀相匹配(即当前分支的基本名称),这样状态栏以及其他通过分支推导前缀的工具才能找到它。务必先运行 tmux ls,确认不存在同名的现有会话;切勿终止已有会话。
tmux 中的窗格索引取决于 pane-base-index(默认为 0;使用常见 dotfile 覆盖设置时为 1)。为确保两种情况下都能正常工作,请在拆分窗格时使用 -P -F '#{pane_id}' 获取每个窗格的 ID,并将这些 ID 用作目标:
REPO=/path/to/workflow--<worktree-suffix>
# Session name = basename of the branch (matches portless's subdomain prefix
# and the statusline's `tmux attach -t <prefix>` indicator). For branch
# `pgp/foo-bar` this resolves to `foo-bar`.
SESSION=$(git -C "$REPO" rev-parse --abbrev-ref HEAD)
SESSION="${SESSION##*/}"
# Create the session and capture the initial pane ID
PANE_DEV=$(tmux new-session -d -s "$SESSION" -c "$REPO" -P -F '#{pane_id}')
PANE_OBS=$(tmux split-window -h -t "$PANE_DEV" -c "$REPO" -P -F '#{pane_id}')
PANE_SH=$(tmux split-window -v -t "$PANE_OBS" -c "$REPO" -P -F '#{pane_id}')
tmux select-layout -t "$SESSION" main-vertical
# Pane DEV (left): Next.js turbopack workbench, with manifest exposed for e2e
tmux send-keys -t "$PANE_DEV" \
'cd workbench/nextjs-turbopack && WORKFLOW_PUBLIC_MANIFEST=1 portless run --name turbopack pnpm dev' C-m
# Pane OBS (top-right): observability UI scoped to the workbench app
tmux send-keys -t "$PANE_OBS" \
'cd workbench/nextjs-turbopack && portless run --name workflow-obs sh -c "pnpm workflow web --webPort \$PORT --noBrowser"' C-m
# Pane SH (bottom-right): scratchpad at repo root
tmux send-keys -t "$PANE_SH" 'echo "scratchpad: $(pwd)"' C-m
tmux attach -t "$SESSION"
两个服务器就绪后,运行 portless list 即可查看路由。使用 portless run 时,每个关联的 worktree 都会获得一个以分支名为前缀的唯一子域名(例如 stepflow-test.turbopack.localhost),因此多个 worktree 可以共存,无需更改配置。
各部分的作用
portless run --name <name>(而不是portless <name> <cmd>):run会自动检测 git worktree,并将经过处理的分支名称作为子域名前缀。--name标志会覆盖推断出的基本名称,同时保留 worktree 前缀。pnpm workflow web --webPort $PORT --noBrowser(而不是在packages/web中运行pnpm dev):打包后的 CLI 会启动针对当前 workbench 应用配置的可观测性 UI,并使用该项目的本地 World 数据为 UI 加载数据。运行packages/web自带的dev脚本虽然会启动 UI,但 UI 不会连接到任何内容。sh -c '... --webPort $PORT':只有当 portless 能在命令行中检测到已知框架时,才会自动注入--port。命令为 CLI 包装器(pnpm workflow web)时,请用sh -c包装,并显式读取$PORT(portless 始终会设置该变量)。- 在开发窗格中设置
WORKFLOW_PUBLIC_MANIFEST=1:端到端测试必须设置此变量,才能从开发服务器获取工作流注册表。 -P -F '#{pane_id}':无论用户的pane-base-index设置如何,都能确保代码片段正确运行(不同配置中的默认值可能不同)。
Claude 状态栏集成
此技能提供了一个状态栏辅助脚本 skills/internal-dev-workbench/statusline.sh,它根据当前分支推导 worktree 前缀,并输出一行精简信息:
dev · obs · tmux attach -t <worktree-prefix>
dev / obs 标签(Nerd Font 火箭 / 图表字形)是 OSC 8 超链接,可在 iTerm2、Kitty、WezTerm、Terminal.app 和 Ghostty 中点击。粗体、带下划线的亮青色样式用于标识链接。tmux 片段(Nerd Font 复制字形)为粗体亮绿色,表示“复制此内容”,而非“点击此处”。只有在存在名称与 worktree 前缀完全一致的会话时才会显示,并且会以完整的可直接粘贴的 tmux attach -t <prefix> 命令形式输出。字体必须包含 Nerd Font 字形,图标才能正确显示;否则会看到替代方框,但布局仍然正常。每个部分互相独立。如果 portless 没有 <prefix>.turbopack.localhost 路由,就会省略 dev 片段,其他部分也同理。如果没有可显示的内容,脚本不会输出任何内容,状态栏将保持静默。
将其接入 ~/.claude/settings.json,使其在所有会话和 worktree 中生效。路径应指向主检出目录,而不是 worktree。worktree 会被删除,因此任何类似 ~/github/vercel/workflow--<branch>/... 的路径都会在删除该 worktree 后失效:
{
"statusLine": {
"type": "command",
"command": "$HOME/github/vercel/workflow/skills/internal-dev-workbench/statusline.sh"
}
}
如果主检出目录位于其他位置,请调整前缀。脚本本身能够感知 worktree:它会从标准输入读取 Claude 的 workspace.current_dir 来推导当前分支,因此从 ~/github/vercel/workflow/... 调用 同一脚本 时,也能正确显示 Claude 会话所在 worktree 的路由。
输出规则:
- 没有可显示的内容(没有匹配的 portless 路由,也没有匹配的 tmux 会话)→ 输出为空。
- 各部分独立显示。启动了服务器但没有 tmux 会话时,只会显示 dev / obs 片段;反之则只会显示 tmux 片段。
- 没有 git 上下文但存在路由 → 回退到第一个匹配的
turbopack/workflow-obs路由,不显示 tmux 指示符。
如果你已经在使用状态栏,并希望追加 internal-dev-workbench 信息,请运行此辅助脚本,并在现有包装脚本中拼接其输出,而不是直接替换 command。
编辑 workflow 文件后重启
workflow 清单会在开发服务器启动时构建。添加到 workbench/example/workflows/*.ts 的新 workflow 或步骤(以及其他 workbench 中对应的符号链接)在运行时不会显示,即使启用了 HMR 也是如此,必须重启开发服务器才会生效。
tmux send-keys -t "$PANE_DEV" C-c
# Wait for the prompt to return
tmux send-keys -t "$PANE_DEV" \
'cd workbench/nextjs-turbopack && WORKFLOW_PUBLIC_MANIFEST=1 portless run --name turbopack pnpm dev' C-m
验证新 workflow 是否已注册(使用 portless list 分配的本地端口,或使用受信任 CA 的 .localhost URL):
/usr/bin/curl -s "$(portless get turbopack)/.well-known/workflow/v1/manifest.json" \
| grep -o '<your-new-workflow>'
对于通过 HTTPS URL 访问的 Node 客户端,如果它们不在 portless 管理的子进程中运行,则需要设置 NODE_EXTRA_CA_CERTS=/tmp/portless/ca.pem。执行过 portless trust 后,浏览器即可正常访问。
针对当前会话运行 e2e 测试
在 scratchpad 窗格中运行。使用 portless 分配的本地端口绕过 TLS,供测试运行器使用:
PORT=$(portless list | awk '/turbopack/ {n=split($3,a,":"); print a[n]; exit}')
DEPLOYMENT_URL="http://localhost:$PORT" APP_NAME="nextjs-turbopack" \
pnpm vitest run packages/core/e2e/e2e.test.ts -t "<test name>"
或者使用已信任 CA 的 portless URL:
NODE_EXTRA_CA_CERTS=/tmp/portless/ca.pem \
DEPLOYMENT_URL="$(portless get turbopack)" APP_NAME="nextjs-turbopack" \
pnpm vitest run packages/core/e2e/e2e.test.ts -t "<test name>"
清理
tmux kill-session -t "$SESSION"
每个子进程退出时,Portless 都会移除对应路由(如果想让 portless list 的输出干净,请先在各窗格中按 Ctrl+C)。代理本身会继续为其他会话运行;如有需要,可使用 portless proxy stop 显式停止。
故障排除
- 开发面板中出现
MODULE_NOT_FOUND: 'workflow':工作区软件包尚未构建。在仓库根目录运行pnpm build,然后重启面板。 - 可观测性界面没有显示运行记录:确认 obs 面板是在
workbench/nextjs-turbopack(或你要检查的其他 workbench)目录中启动的。CLI 会读取当前工作目录中的本地 World。 - react-router 使用
:5173,而不是 portless 端口:这是因为 obs 面板使用了packages/web中的pnpm dev。请改用上文的pnpm workflow web --webPort $PORT形式。 - 启动时出现 Source-map 警告(
failed to read input source map ... packages/serde/dist/index.js.map):此警告无害,不会阻碍开发。 - 编辑
99_e2e.ts后工作流注册信息过时:重启开发面板;HMR 不会重建清单。 - 状态栏没有显示内容:确认
portless list至少有一个匹配的路由,settings.json中的路径是绝对路径,且脚本具有可执行权限(chmod +x)。