✨ feat(vscode): 看板默认显示团队工单,同事的卡只读并贴头像

看板范围默认从「只看我的」改为「团队全部」,同事移交或推进的工单不用
手动切开关就能看见。同事的卡片按 Gitea 归属(assignee 优先,无 assignee
退回创建者)判定为只读:禁止拖列、开会话、实施、合并分支、删 worktree、
删改工单,卡片右上角贴归属人头像,详情面板属性表整体消毒并显示只读徽章。
看 PR 改动、勾已查看、生成文件摘要属于本机审查动作,照常放行。

扩展侧在消息分发前统一拦截需要归属人身份的消息,兜住前端快照过期或工单
刚被重新指派的竞态;webhook 单卡上板时同步归属,避免增量卡漏进门禁。
头像由扩展侧带 token 抓回转 data URI 下发,绕开 webview CSP 对外链图片
的限制,按 URL 进程内缓存。

Claude-Session: https://claude.ai/code/session_01KSgcQwf1mWrb8qeTKFvJbV
This commit is contained in:
cruldra committed 2026-09-10 15:30:02 +08:00
1 parent e8963ac2a2
commit 9167b86708
26 files changed
+550 -34

No files matched your search

+7 -4
View File
@@ -26,7 +26,7 @@ import { mergeStateJsonComment, mergeStateJsonCommentGuarded, readStateJsonComme
import { getLocalIssueState, mergeLocalIssueState, overlayLocalIssueState } from '../issues/localState'
import { logger } from '../logging/logger'
import { hasLiveIssueSessionTerminal } from '../panel/handlers/terminals'
import { KANBAN_SCOPE_KEY } from '../panel/kanbanScope'
import { DEFAULT_KANBAN_SCOPE, KANBAN_SCOPE_KEY } from '../panel/kanbanScope'
import { annotateReviewSessionFileExists } from '../sessions/codexSessions'
import { getSettings } from '../settings/store'
import { decideEventOwnership } from './eventOwnership'
@@ -622,7 +622,7 @@ class WebhookCoordinator {
// 广播到所有机器 → append 前按同一套可见性规则过门禁,否则别人的卡
// 会凭空上屏、刷新后又消失。身份/payload 只在 'mine' 且非本机创建时
// 才解析,省一次身份请求。
const scope = this.ctx.workspaceState.get<KanbanScope>(KANBAN_SCOPE_KEY) ?? 'mine'
const scope = this.ctx.workspaceState.get<KanbanScope>(KANBAN_SCOPE_KEY) ?? DEFAULT_KANBAN_SCOPE
const pendingCreatedLocally = pending !== undefined
let poster: string | undefined
let assignees: string[] = []
@@ -653,6 +653,7 @@ class WebhookCoordinator {
})
const issue = loaded ? (await annotateReviewSessionFileExists(overlayLocalIssueState(this.ctx, [loaded], ws)))[0] : null
if (issue) {
this.activePanel.noteIssueOwnership(issue)
this.activePanel.postMessage({ type: 'issue/append', issue, select: pending ? true : undefined })
if (pending) {
this.activePanel.postMessage({
@@ -728,7 +729,7 @@ class WebhookCoordinator {
// 与 handleIssueOpened 同一套 'mine' 可见性门禁:指派给我 → 上卡,
// 与我无关(含被取消指派)→ 撤卡。身份/payload 只在 'mine' 时解析,
// 省一次身份请求。
const scope = this.ctx.workspaceState.get<KanbanScope>(KANBAN_SCOPE_KEY) ?? 'mine'
const scope = this.ctx.workspaceState.get<KanbanScope>(KANBAN_SCOPE_KEY) ?? DEFAULT_KANBAN_SCOPE
let poster: string | undefined
let assignees: string[] = []
let me: string | undefined
@@ -761,8 +762,10 @@ class WebhookCoordinator {
const issue = loaded ? (await annotateReviewSessionFileExists(overlayLocalIssueState(this.ctx, [loaded], ws)))[0] : null
// 前端对 issue/append 按 number 去重原位替换:已在板上的卡刷新
// assignee,不在板上的新增——指派给我的工单借此实时上卡。
if (issue)
if (issue) {
this.activePanel.noteIssueOwnership(issue)
this.activePanel.postMessage({ type: 'issue/append', issue })
}
}
catch (err) {
const msg = err instanceof Error ? err.message : String(err)