mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-08-03 13:24:41 +00:00
9.8 KiB
9.8 KiB
Web Shell GitHub PR 只读列表
问题陈述
Web Shell 已有 Git 能力(状态芯片、/diff、/log 对话框、Git mode
popover),但用户想看当前仓库的开放 PR(状态、CI、review)时必须打断会话
问 agent,或切到浏览器。目标是在现有 Git 对话框中增加一个只读
"Pull requests" 标签页,让用户不打断会话即可瞟一眼 PR 状态。
范围边界(已与需求方确认):
- 做:只读开放 PR 列表(标题、分支、作者、review 状态、CI 聚合状态、 更新时间),点击跳转 github.com。
- 不做:Issue 面板、创建 PR / 提交 review 的表单(agent 用
gh已能 完成,重复建设)、PR 详情展开、分页、自动刷新。
现状
- daemon 有三层路线模式:
packages/cli/src/serve/routes/workspace-git*.ts用resolveWorkspaceRuntimeFromParam+requireTrustedWorkspaceRuntime做 workspace 路由与 trust 门控;核心逻辑在 core (packages/core/src/utils/gitDiff.ts的fetchGitLog等,execFile- 超时);SDK
WorkspaceDaemonClient经workspaceJsonRequest暴露 类型化方法。
- 超时);SDK
- daemon 侧 GitHub 先例只有
POST /workspace/setup-github(写 workflow) 与技能安装(直连 GitHub API,用GH_TOKEN/GITHUB_TOKEN)。 - Web Shell 的
GitDialog是 tab 容器('diff' | 'log'两个 view), 由/diff、/log本地命令和侧栏打开;内容组件 (GitDiffContent/GitLogContent)经client.workspaceByCwd(cwd).workspaceGit*()取数。 - capability 通过
SERVE_CAPABILITY_REGISTRY登记,Web Shell 用capabilities?.features?.includes(tag)门控 UI。
方案
1. core:packages/core/src/utils/github-prs.ts(新)
fetchGitHubPullRequests(cwd),经 execFile(参数数组,无 shell 插值,
windowsHide,10s 超时)执行:
gh pr list --state open --limit 30 --json number,title,url,author,headRefName,isDraft,reviewDecision,statusCheckRollup,updatedAt
设计决定:
- 用
ghCLI 而非直连 REST API。AGENTS.md 规定 GitHub 操作一律走gh;复用用户已有的gh auth登录,无需 token 配置 UI;gh自动识别 fork 的 upstream remote;statusCheckRollup一次调用拿到 CI 聚合。 - 先
git rev-parse --is-inside-work-tree探活:非 git 仓库返回{ kind: 'not_a_repo' },避免把 "not a git repository" 的 gh stderr 当错误。 - 结果判别:
ENOENT(gh 未安装)→{ kind: 'cli_unavailable' }- gh 非零退出(未登录、无 GitHub remote、网络)→
{ kind: 'failed', message }(stderr 截断 512 字符) - 成功 → 按
updatedAt降序排序,逐项归约:state:'open' | 'draft'(由isDraft得出)reviewDecision:'approved' | 'changes_requested' | 'review_required' | nullchecks:'passing' | 'failing' | 'pending' | 'none'—— 在 daemon 侧聚合statusCheckRollup,面板只渲染一个图标,不下发原始 rollup 数组。failure 结论(CheckRun 的 FAILURE/CANCELLED/TIMED_OUT/ ACTION_REQUIRED/STARTUP_FAILURE/STALE,StatusContext 的 ERROR/FAILURE, 与gh pr checks的口径一致:cancelled/stale 同样阻塞合并)优先, 其次未完成(conclusion 为空 / PENDING / EXPECTED),再次成功类 (SUCCESS/NEUTRAL/SKIPPED);空数组为'none'。
- 返回判别联合,不抛异常;错误语义由路由层翻译。
2. daemon 路由:packages/cli/src/serve/routes/workspace-github-prs.ts(新)
GET /workspaces/:workspace/github/prs
- 严格复用
workspace-git-log.ts模式:resolveWorkspaceRuntimeFromParamrequireTrustedWorkspaceRuntime,只注册 workspace-qualified 形态 (Web Shell 只走workspaceByCwd;无旧客户端需要单数形态)。
- 响应(均带
applyReadHeaders):- 200
{ v: 1, workspaceCwd, available: true, pullRequests: [...] } - 200
{ v: 1, workspaceCwd, available: false, pullRequests: [] }(非 git 仓库;pullRequests恒在,与DaemonGitLog.entries风格一致) - 502
{ error, code: 'github_cli_unavailable' }(gh 未安装) - 502
{ error, code: 'github_prs_failed' }(gh 失败;message 中的 workspace 路径替换为<workspace>,同 setup-github 的 sanitize 先例)
- 200
- 不做缓存与轮询;每次打开对话框拉一次。
- capability 登记
workspace_github_prs: { since: 'v1' }(无条件 tag: 路由契约恒存在,运行时可用性由错误码表达——与workspace_github_setup一致)。 server.ts注册,紧邻 git 路由。
3. SDK:packages/sdk-typescript/src/daemon/
types.ts:DaemonGitHubPullRequest:{ number, title, url, author, headRefName, state: 'open'|'draft', reviewDecision: ... | null, checks: ..., updatedAt: number }(updatedAt为 epoch 秒)DaemonGitHubPullRequestList:{ v: 1, workspaceCwd, available: boolean, pullRequests: DaemonGitHubPullRequest[] }(pullRequests恒在,不可用时为 空数组)
DaemonClient.ts的WorkspaceDaemonClient增加workspaceGitHubPullRequests(),走workspaceJsonRequest(..., '/github/prs', 'GET /workspaces/:workspace/github/prs', { mode: 'rest' })。
4. Web Shell
client/components/dialogs/GitHubPrsDialog.tsx(新):导出GitHubPrsContent({ workspaceCwd, onSubtitleChange }签名与GitLogContent一致)。- 挂载时经
client.workspaceByCwd(workspaceCwd) .workspaceGitHubPullRequests()拉取;渲染行:状态图标(open/draft)、#number 标题、分支、作者、相对更新时间、review 徽标、CI 图标。 - 行点击
window.open(url, '_blank', 'noopener')。 - 状态文案:loading / 空列表 /
available:false(非 git 仓库)/github_cli_unavailable(引导安装 gh)/ 其他失败(静态错误文案,重新 打开对话框即重试,与 GitLog 一致)。 - 副标题上报
N 个开放 PR。
- 挂载时经
GitDialog.tsx:view 联合加'prs',第三个 tab (githubPrs.title,中英文 "Pull requests"/"拉取请求");tab 键盘导航 (ArrowLeft/Right/Home/End)适配三个 tab。- 入口:
- 本地命令
/prs(constants/localCommands.ts+ App.tsx 分支,仿/log;无 workspace 时 toast),打开 GitDialogview: 'prs'。 - capability
workspace_github_prs不存在时隐藏对话框里的 PR tab(旧 daemon 404 防护);/prs命令仍列出,执行时 toast 提示不支持。
- 本地命令
- i18n:
githubPrs.*、local.prs、localCommand.prsNoWorkspace中英文。
5. 测试
| 层 | 文件 | 覆盖 |
|---|---|---|
| core | utils/github-prs.test.ts |
mock execFile:成功解析/排序/checks 聚合/reviewDecision 映射;非 repo;ENOENT;gh 失败 |
| cli | serve/routes/workspace-github-prs.test.ts |
mock core 函数:200 两形态、untrusted 403、unknown 400、cli_unavailable 502、failed 502 + 路径脱敏 |
| sdk | DaemonClient 测试 | 方法命中正确路径与 query |
| web-shell | GitHubPrsDialog.test.tsx、GitDialog.test.tsx、App 命令测试 |
各状态渲染、tab 切换、capability 门控、/prs 打开对话框 |
| e2e | .qwen/e2e-tests/web-shell-github-prs.md |
真 daemon + 本仓库(真 gh)端到端;mockDaemon 补路由 |
关键文件
| 文件 | 动作 |
|---|---|
packages/core/src/utils/github-prs.ts |
新增 |
packages/core/src/index.ts(或对应导出面) |
导出 |
packages/cli/src/serve/routes/workspace-github-prs.ts |
新增 |
packages/cli/src/serve/capabilities.ts |
登记 tag |
packages/cli/src/serve/server.ts |
注册路由 |
packages/sdk-typescript/src/daemon/types.ts / DaemonClient.ts |
类型 + 方法 |
packages/web-shell/client/components/dialogs/GitHubPrsDialog.tsx |
新增 |
packages/web-shell/client/components/dialogs/GitDialog.tsx |
第三 tab |
packages/web-shell/client/App.tsx / constants/localCommands.ts / i18n.tsx |
入口 + 文案 |
性能优化:daemon 侧 TTL 缓存
gh pr list --json …,statusCheckRollup 是慢点——为 30 个 PR 各拉 CI rollup
要多秒级 GitHub 往返(实测本仓库冷启动 ~8s,CPU 时间仅 ~0.07s,纯网络
I/O)。面板是"瞟一眼"性质,数据 stale 一分钟完全可接受,故在 daemon 路由
加闭包级、按 workspaceCwd 分键的短 TTL 缓存(默认 60s,cacheTtlMs 可注入,
对齐 usage-stats.ts 的既有约定):
- 单飞合并:进行中的 load 无论新旧都复用,并发打开共享一次
gh拉起 (即使它超过 TTL);TTL 窗口从 load 落定后开始计。 - 仅缓存
ok:cli_unavailable/failed/not_a_repo落定即清条目, 下次打开重试(用户可能刚装好/登录 gh,或工作区刚变成仓库)。 - 实测:冷启动 8.0s → 缓存命中 ~2ms(约 2500 倍)。
首次打开仍慢(~8s)是 gh 本身的延迟;如需改善首屏,后续可做两阶段加载
(先不带 statusCheckRollup 快速渲染列表,再异步补 CI 图标),不在本次范围。
开放问题
- 无。
gh缺失/未登录、非 git 仓库、旧 daemon 三条降级路径均有明确 UI 语义。