Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
026e694
feat(web-shell): Bind GitHub PRs to sessions with sidebar badge and s…
wenshao Aug 20, 2026
7049f44
feat(web-shell): Support multiple PR bindings per session
wenshao Aug 20, 2026
e6f8263
Merge origin/main into feat/webshell-session-pr-binding
wenshao Aug 20, 2026
f608f8e
Merge origin/main into feat/webshell-session-pr-binding
wenshao Aug 20, 2026
8feef93
feat(web-shell): Show PR badges in the session overview and picker di…
wenshao Aug 20, 2026
31ea3da
Merge origin/main into feat/webshell-session-pr-binding
wenshao Aug 20, 2026
031f4fc
fix(web-shell): Match the overview PR badge color to the sidebar accent
wenshao Aug 20, 2026
a60b0db
fix(web-shell): Address review findings on PR bindings
wenshao Aug 20, 2026
d8900b1
Merge origin/main into feat/webshell-session-pr-binding
wenshao Aug 20, 2026
29e3a1f
fix(core): harden session pr sidecar persistence and moves (#9543)
qwen-code-dev-bot Aug 20, 2026
7e0e665
Merge branch 'main' into feat/webshell-session-pr-binding
wenshao Aug 21, 2026
056f866
Merge remote-tracking branch 'origin/main' into feat/webshell-session…
qwen-code-dev-bot Aug 21, 2026
3f66720
fix(serve): align session pr echoes with the persisted sidecar (#9543)
qwen-code-dev-bot Aug 21, 2026
76b892a
chore(desktop): regenerate bun.lock to match workspace versions
wenshao Aug 21, 2026
9375619
Merge remote-tracking branch 'wenshao/feat/webshell-session-pr-bindin…
wenshao Aug 21, 2026
5419305
fix(web-shell): address R2 review findings on PR bindings
wenshao Aug 21, 2026
9c3c171
Merge origin/main into feat/webshell-session-pr-binding
wenshao Aug 21, 2026
f489d2c
fix(serve): address R5 review findings on PR bindings
wenshao Aug 21, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 86 additions & 0 deletions docs/design/2026-08-20-webshell-session-pr-binding.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# Web Shell 会话绑定 GitHub PR 号

日期:2026-08-20
状态:已确认 MVP 范围

## 问题

Web Shell 同时运行 20+ 会话时,侧栏信息不足以回答"哪个会话对应 PR #N"。
当前链路全断:

1. `GitDialog.doCreatePr` 创建 PR 拿到 `{url, number}` 后只显示状态消息,不回写(`packages/web-shell/client/components/dialogs/GitDialog.tsx:502-553`)。
2. `DaemonSessionSummary` / `BridgeSessionSummary` 无 PR 字段;`updateSessionMetadata` 只放行 `displayName`。
3. 侧栏搜索只匹配标题和 sessionId(`WebShellSidebar.tsx:3289-3302`),不匹配分支名、worktree slug、PR 号。
4. 无任何持久化载体,daemon 重启后即使内存绑定也会丢。

## 方案

### 数据模型

`DaemonSessionSummary` 与 `BridgeSessionSummary`(镜像,需同步)增加:

```json
"prs": [{ "number": 9517, "url": "https://github.com/owner/repo/pull/9517" }]
```

- 一个会话可能创建多个 PR(stacked PR、连续修复),`prs` 按绑定时间排序(最后一个 = 最新),上限 10 个(超出丢弃最旧)。同号重复绑定刷新 url 并移到最新位。
- `number`:正整数;`url`:http(s) URL(badge/tooltip 直接作为链接目标渲染,拒绝 `javascript:` 等 scheme——route、bridge、SDK 校验器、sidecar 校验四层统一要求)。
- 字段可选、可缺省;不提供"清除"语义。
- 写入 API 保持单条:`updateSessionMetadata(sessionId, { pr: {number, url} })` 每次绑定一个,daemon 负责 upsert 进列表;读取/事件/响应均为完整 `prs` 数组。

### 写入端

- SDK `DaemonClient.updateSessionMetadata` 的 metadata 参数扩展 `pr?: { number: number; url: string }`(单条);响应解析完整 `prs` 数组。
- daemon 两个 PATCH metadata 路由(`/session/:id/metadata` 与 workspace 作用域版本)校验 `pr` 后透传,bridge 更新成功后将 sidecar upsert 的完整列表回显在响应里。
- bridge `updateSessionMetadata`(`packages/acp-bridge/src/bridge.ts`)先做全部校验再变更(组合请求不允许部分生效);upsert 进 live entry.prs(去重按 number,上限 10),`session_metadata_updated` SSE 事件 data 带完整 `prs`。
- ACP `session/update_metadata`(`acp-http/dispatch.ts`)同样把最新绑定 upsert 进 sidecar。
- `GitDialog.doCreatePr` 成功后:仅用 dialog 已有的 `sessionId`(`sessionIdRef.current`,即连接会话或 dialog 已为提交信息生成等操作解析出的会话)调用 `updateSessionMetadata(sessionId, { pr })`。**不调 `resolveSessionForWorkspace`**——它可能创建幽灵会话或误绑"最近会话"。写入失败仅降级为 console 警告,不影响 PR 创建成功的状态展示。

### 持久化

新增 sidecar `<chatsDir>/<sessionId>.pr.json`,复刻 worktree sidecar 模式:

- 新 core 服务 `packages/core/src/services/session-pr-service.ts`:`SessionPr` 接口、数组 schema 校验(`{prs: [...]}`,容忍 ENOENT/JSON 损坏)、`readSessionPrs` / `writeSessionPrs` / `upsertSessionPr`(按 number 去重、移到最新、cap 10)。
- `SessionService` 增加 `getPrSessionPathForArchiveState` 路径助手;归档/取消归档移动 sidecar、删除会话时清理(与 worktree sidecar 一一对应)。
- `session-list.ts` 的 `enrichPrSidecars` 回填 persisted summary 的 `prs`;live 会话的 entry.prs 只含本 daemon 生命周期内的绑定,回填时与 sidecar 历史按 number 合并(live 的 url 优先,live-only 的排最后)。

### 展示与搜索(web-shell 侧栏)

- `renderSessionRow`:会话行标题旁渲染小号 badge(`session.prs` 非空时),显示最新 PR 号,多于一个时追加 `+N`;点击经 `useExternalLinkOpener` 打开最新 PR(desktop webview 下 `target="_blank"` 会被静默丢弃);click/doubleClick/keydown 均 stopPropagation(双击 badge 不触发重命名)。
- `SessionDetailsTooltip`:列出全部绑定 PR(最新在前),各为外链。
- `filteredSessions` 匹配逻辑扩展:`label`、`sessionId` 之外,增加**任意一个**绑定 PR 号(输入 `9517` 或 `#9517` 都命中)、`branch.name`、`worktree.branch`、`worktree.slug`(`sessionMatchesGitQuery`,WebShellSidebar 与 WorkspaceSection 共用)。
- SSE 消费侧:web-shell 不直接消费 `session_metadata_updated` 更新 store;bridge 的 `markSessionCatalogChanged()` 触发 catalog revision bump,侧栏 live-state 轮询(2s 周期)发现后自动 refetch——badge 在绑定后 ~2s 内出现(与改名等其他客户端变更的传播机制一致)。
- i18n:新增 `sidebar.sessionPr` / `sidebar.sessionPrMultiple` 两个 key(EN/ZH)。

## 关键决策

- **绑定时机 = GitDialog 创建 PR 成功时**。Agent 在 shell 里自行 `gh pr create` 的路径无法拦截,MVP 不覆盖;用户主力流程是 GitDialog。
- **sidecar 而非 transcript 记录**:displayName 走 `custom_title` transcript 记录是因为标题属于会话内容流;PR 绑定是会话外部元数据,worktree sidecar 是同类先例,改动面更小。
- **多 PR 列表(cap 10)**:一个会话可能创建多个 PR(stacked PR、连续修复),只保留最新一个会让"按 PR 号反查会话"在这些场景失效。绑定按 number 去重、重复绑定移到最新位;badge 显示最新号 + `+N`,tooltip 列全部,搜索匹配任意一个。上限 10 防无界增长。
- **workspace 级打开 GitDialog(无会话上下文)时不回写**:dialog 没有已解析的会话就跳过,不报错;绝不通过 `resolveSessionForWorkspace` 创建新会话来绑定(会产生幽灵会话/误绑)。

## 影响文件

| 层 | 文件 |
| ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| SDK 类型 | `packages/sdk-typescript/src/daemon/types.ts`(DaemonSessionSummary.pr) |
| SDK 事件 | `packages/sdk-typescript/src/daemon/events.ts`(MetadataUpdated data + 校验) |
| SDK 客户端 | `packages/sdk-typescript/src/daemon/DaemonClient.ts`(updateSessionMetadata 参数) |
| bridge 类型 | `packages/acp-bridge/src/bridgeTypes.ts`(BridgeSessionSummary.pr、metadata 参数) |
| bridge | `packages/acp-bridge/src/bridge.ts`(updateSessionMetadata 校验/存储/广播) |
| core | `packages/core/src/services/session-pr-service.ts`(新增)+ SessionService 路径助手/归档移动/删除清理 |
| daemon 路由 | `packages/cli/src/serve/routes/session.ts`(两个 PATCH 路由校验 + sidecar 写入)、`acp-http/dispatch.ts`(ACP `session/update_metadata` 的 sidecar 写入) |
| daemon 列表 | `packages/cli/src/serve/server/session-list.ts`(enrichPrSidecars) |
| web-shell | `GitDialog.tsx`(回写)、`WebShellSidebar.tsx`(badge + 搜索)、`SessionDetailsTooltip.tsx`(PR 行)、locale 文件 |
| 测试 | 上述各层的 collocated 单测 |

## 范围边界(明确不做)

- 服务端分页过滤(20+ 会话规模客户端搜索足够;`sourceType/sourceId` 过滤管道是将来扩展的样板)。
- 历史会话迁移;CLI `--worktree=#<pr>` 的 `pr-<n>` slug 由搜索匹配 slug 顺带覆盖。
- 纯 branch 会话 branch 信息重启丢失的独立 bug,另行处理。
- Agent shell 内 `gh pr create` 的自动发现。

## 开放问题

无。
11 changes: 11 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading