✨ feat(vscode): 五种会话 id 行都能用橡皮擦清除本机记录,别的机器上的旧会话不再挡着新起

This commit is contained in:
cruldra committed 2026-09-24 19:29:39 +08:00
1 parent 4d905ff16e
commit d604b6f426
12 files changed
+169 -64

No files matched your search

+2 -2
View File
@@ -63,7 +63,7 @@ export function App() {
closeIssue,
closeSessionTab,
continueBrainstorm,
clearBrainstormSession,
clearSession,
changeColumn,
setDependency,
clearDependency,
@@ -419,7 +419,7 @@ export function App() {
onResetToTodo={n => changeColumn(n, 'todo', selectedIssue?.source, selectedIssue?.externalId)}
onCloseSessionTab={closeSessionTab}
onContinueBrainstorm={continueBrainstorm}
onClearBrainstormSession={clearBrainstormSession}
onClearSession={clearSession}
onUpdateAutoReview={updateIssueAutoReview}
onUpdateProfilePath={updateIssueProfilePath}
onUpdateBrainstormProfilePath={updateIssueBrainstormProfilePath}
@@ -50,7 +50,7 @@ interface BottomTabsProps {
onResetToTodo: (issueNumber: number) => void
onCloseSessionTab: (issueNumber: number, kind: 'brainstorm' | 'implement' | 'review' | 'test' | 'prototype') => void
onContinueBrainstorm: (issueNumber: number) => void
onClearBrainstormSession: (issueNumber: number) => void
onClearSession: (issueNumber: number, kind: 'brainstorm' | 'implement' | 'review' | 'test' | 'prototype') => void
onUpdateAutoReview: (issueNumber: number, value: boolean) => void
onUpdateProfilePath: (issueNumber: number, profilePath: string) => void
onUpdateBrainstormProfilePath: (issueNumber: number, brainstormProfilePath: string) => void
@@ -154,7 +154,7 @@ export function BottomTabs(props: BottomTabsProps) {
onResetToTodo={props.onResetToTodo}
onCloseSessionTab={props.onCloseSessionTab}
onContinueBrainstorm={props.onContinueBrainstorm}
onClearBrainstormSession={props.onClearBrainstormSession}
onClearSession={props.onClearSession}
onUpdateAutoReview={props.onUpdateAutoReview}
onUpdateProfilePath={props.onUpdateProfilePath}
onUpdateBrainstormProfilePath={props.onUpdateBrainstormProfilePath}
@@ -115,7 +115,8 @@ interface IssueDetailPanelProps {
* 打进去,关了就 `claude --resume <id>` 带着提示词恢复,没会话就新开一个。
*/
onContinueBrainstorm: (issueNumber: number) => void
onClearBrainstormSession: (issueNumber: number) => void
/** 清除某行的会话 id:扩展侧弹确认框,只清本机记录,之后该行可新起一个。 */
onClearSession: (issueNumber: number, kind: 'brainstorm' | 'implement' | 'review' | 'test' | 'prototype') => void
/** Persist a per-issue `autoReview` override into the state JSON. */
onUpdateAutoReview: (issueNumber: number, value: boolean) => void
/** 已知 Claude profile 列表,用于「配置文件」下拉选项。 */
@@ -173,7 +174,7 @@ export function IssueDetailPanel({
onResetToTodo,
onCloseSessionTab,
onContinueBrainstorm,
onClearBrainstormSession,
onClearSession,
onUpdateAutoReview,
profiles,
onUpdateProfilePath,
@@ -213,6 +214,14 @@ export function IssueDetailPanel({
return true
}
// 五行会话 id 同一规则:有 id 就给 Eraser,排在该行按钮最后。会话文件在别的
// 机器上时,resume 不了的旧 id 靠它清掉,才能新起一个。
const clearSessionAction = (issueNumber: number, kind: 'brainstorm' | 'implement' | 'review' | 'test' | 'prototype'): RowAction => ({
icon: <Eraser className="size-3.5" />,
title: '清除会话 id:只清本机记录,之后可新起一个',
onClick: () => onClearSession(issueNumber, kind),
})
const schema = useMemo<PropertyGroup[]>(
() => [
{
@@ -246,6 +255,7 @@ export function IssueDetailPanel({
// - 工单未完成 → 一直有 ▶ 续聊。插件把续聊提示词送进对应会话:
// 规划终端开着就直接打进去,关了就 --resume 带提示词,没会话就新开。
// - brainstormTabOpen → 再加一个 X 关闭 tab
// - 有会话 id → 最后再加一个 Eraser 清除本机记录
// - 会话 id 本机没有会话文件 → 只读不可双击,续聊会新开会话
const hasSession = !!(issue?.sessionId && issue.sessionId.length > 0)
const missing = isSessionMissing(issue, issue?.sessionId)
@@ -271,16 +281,8 @@ export function IssueDetailPanel({
},
})
}
if (hasSession) {
secondaryActions.push({
icon: <Eraser className="size-3.5" />,
title: '清除会话:下次续聊新开一个,不再带旧会话的历史',
onClick: () => {
if (issue)
onClearBrainstormSession(issue.number)
},
})
}
if (hasSession && issue)
secondaryActions.push(clearSessionAction(issue.number, 'brainstorm'))
if (missing) {
return {
key: 'sessionId',
@@ -288,7 +290,6 @@ export function IssueDetailPanel({
type: 'action' as const,
readOnly: true,
description: canContinue ? `${MISSING_SESSION_DESC};点 ▶ 续聊会新开一个规划会话` : MISSING_SESSION_DESC,
actionIcon: <Terminal className="size-3.5" />,
secondaryActions,
}
}
@@ -316,8 +317,9 @@ export function IssueDetailPanel({
// extension 端 handleResumeSession 会在 worktree 路径不存在时退回
// 工作区根目录,并通过 toast 告知用户;description 在两种状态下
// 给出不同的提示文案。
// 本机没有会话文件时只读且不给 ▶:重新实施会先删远程 feature 分支,
// 会把别的机器上的实施进度一并抹掉。
// 有会话 id 就在最后加 Eraser 清除本机记录。本机没有会话文件时只读且
// 不直接给 ▶:重新实施会先删远程 feature 分支,会把别的机器上的实施
// 进度一并抹掉;确要重来得先用 Eraser 清掉 id,▶ 再按无会话的规则出现。
const secondaryActions: RowAction[] = (() => {
if (issue?.implementTabOpen) {
return [{
@@ -339,6 +341,8 @@ export function IssueDetailPanel({
onClick: () => onImplement(issue.number, issue.planFile ?? '', issue.profilePath, issue.sessionId),
}]
})()
if (issue?.implementSessionId)
secondaryActions.push(clearSessionAction(issue.number, 'implement'))
if (isSessionMissing(issue, issue?.implementSessionId)) {
return {
key: 'implementSessionId',
@@ -346,7 +350,6 @@ export function IssueDetailPanel({
type: 'action' as const,
readOnly: true,
description: `${MISSING_SESSION_DESC};要在本机接着实施,请让对方把工单移交过来`,
actionIcon: <Terminal className="size-3.5" />,
secondaryActions,
}
}
@@ -384,13 +387,16 @@ export function IssueDetailPanel({
return
onResumeReviewSession(v, issue.number, issue?.worktreePath)
},
secondaryActions: issue.reviewTabOpen
? [{
icon: <X className="size-3.5" />,
title: '关闭审查 tab',
onClick: () => onCloseSessionTab(issue.number, 'review'),
}]
: [],
secondaryActions: [
...(issue.reviewTabOpen
? [{
icon: <X className="size-3.5" />,
title: '关闭审查 tab',
onClick: () => onCloseSessionTab(issue.number, 'review'),
}]
: []),
clearSessionAction(issue.number, 'review'),
],
}
: {
key: 'reviewSessionId',
@@ -400,14 +406,17 @@ export function IssueDetailPanel({
description: issue?.reviewSessionId
? `${MISSING_SESSION_DESC};点 ▶ 对当前 PR 重新起一个审查会话`
: '还没有审查会话;点 ▶ 对当前 PR 启动 codex 审查(等同 PR 事件自动触发的那次)',
secondaryActions: issue?.pr
? [{
icon: <Play className="size-3.5" />,
title: locked ? lockTitle : '启动审查会话',
disabled: locked,
onClick: () => onStartReviewSession(issue.number),
}]
: [],
secondaryActions: [
...(issue?.pr
? [{
icon: <Play className="size-3.5" />,
title: locked ? lockTitle : '启动审查会话',
disabled: locked,
onClick: () => onStartReviewSession(issue.number),
}]
: []),
...(issue?.reviewSessionId ? [clearSessionAction(issue.number, 'review')] : []),
],
},
(() => {
// 测试会话 id 行有三种状态(照搬头脑风暴行的三态):
@@ -419,6 +428,7 @@ export function IssueDetailPanel({
// 那条路径要靠 PR 让 cc 了解合并进来的改动。
// 会话 id 本机没有会话文件 → 只读不可双击,按状态 3 的条件给 ▶ 重新起一个
// (启动会覆盖旧 testSessionId)。
// 有会话 id 时最后再加一个 Eraser 清除本机记录。
const hasSession = !!(issue?.testSessionId && issue.testSessionId.length > 0)
const missing = isSessionMissing(issue, issue?.testSessionId)
const canStart = (!hasSession || missing) && (!!issue?.worktreeExists || !!issue?.pr)
@@ -443,6 +453,8 @@ export function IssueDetailPanel({
},
})
}
if (hasSession && issue)
secondaryActions.push(clearSessionAction(issue.number, 'test'))
if (missing) {
return {
key: 'testSessionId',
@@ -450,7 +462,6 @@ export function IssueDetailPanel({
type: 'action' as const,
readOnly: true,
description: `${MISSING_SESSION_DESC};点 ▶ 新起一个测试会话(worktree 还在就在 worktree 里测,已清理则需要工单有已合并的 PR)`,
actionIcon: <Terminal className="size-3.5" />,
secondaryActions,
}
}
@@ -477,7 +488,8 @@ export function IssueDetailPanel({
// 原型会话 id 行:与测试行同构的三态,差别是启动不看 PR——原型只照
// 工单正文画界面,任何阶段都能起;前置工单没做完时按钮禁用。
// 会话 id 本机没有会话文件 → 只读不可双击,同样给 ▶ 重新起一个
// (启动会覆盖旧 prototypeSessionId)。
// (启动会覆盖旧 prototypeSessionId)。有会话 id 时最后再加一个 Eraser
// 清除本机记录。
const hasSession = !!(issue?.prototypeSessionId && issue.prototypeSessionId.length > 0)
const missing = isSessionMissing(issue, issue?.prototypeSessionId)
const secondaryActions: RowAction[] = []
@@ -502,6 +514,8 @@ export function IssueDetailPanel({
},
})
}
if (hasSession && issue)
secondaryActions.push(clearSessionAction(issue.number, 'prototype'))
if (missing) {
return {
key: 'prototypeSessionId',
@@ -509,7 +523,6 @@ export function IssueDetailPanel({
type: 'action' as const,
readOnly: true,
description: `${MISSING_SESSION_DESC};点 ▶ 新起一个原型会话`,
actionIcon: <Terminal className="size-3.5" />,
secondaryActions,
}
}
@@ -729,7 +742,7 @@ export function IssueDetailPanel({
],
},
],
[onResumeSession, onResumeReviewSession, onResumeTestSession, onStartTestSession, onResumePrototypeSession, onStartPrototypeSession, onOpenFile, onOpenPrototypeInFigma, onImplement, onOpenPr, onGeneratePrDiffSummary, onOpenWorktree, onDeleteWorktree, onMergeBranch, onCloseSessionTab, onContinueBrainstorm, onClearBrainstormSession, onOpenLogs, issue, locked, lockTitle, globalAutoReview, prDiffSummaryRunning, profiles, isSessionTabPending, onStartReviewSession],
[onResumeSession, onResumeReviewSession, onResumeTestSession, onStartTestSession, onResumePrototypeSession, onStartPrototypeSession, onOpenFile, onOpenPrototypeInFigma, onImplement, onOpenPr, onGeneratePrDiffSummary, onOpenWorktree, onDeleteWorktree, onMergeBranch, onCloseSessionTab, onContinueBrainstorm, onClearSession, onOpenLogs, issue, locked, lockTitle, globalAutoReview, prDiffSummaryRunning, profiles, isSessionTabPending, onStartReviewSession],
)
// 同事的工单只读:属性表统一消毒,顶部只留查看类按钮。
+10 -7
View File
@@ -241,8 +241,8 @@ export interface UseIssuesResult {
* postMessage 持久化到 state JSON。失败时扩展端 push `issue/patch` 回滚。
*/
updateIssueBrainstormProfilePath: (issueNumber: number, brainstormProfilePath: string) => void
/** 清掉工单记的头脑风暴会话 id,下次续聊新开会话。 */
clearBrainstormSession: (issueNumber: number) => void
/** 清掉工单在本机记的某种会话 id,之后该行可新起一个会话。 */
clearSession: (issueNumber: number, kind: 'brainstorm' | 'implement' | 'review' | 'test' | 'prototype') => void
/**
* 覆盖该工单测试会话专用的 Claude 配置文件,乐观更新本地 state 后
* postMessage 持久化到 state JSON。失败时扩展端 push `issue/patch` 回滚。
@@ -736,11 +736,14 @@ export function useIssues(): UseIssuesResult {
postMessage({ type: 'issue/update-brainstorm-profile-path', issueNumber, brainstormProfilePath })
}, [])
// 清除头脑风暴会话 id:扩展侧先弹确认框,确认后才落库并推 issue/patch,
// 清除会话 id:扩展侧先弹确认框,确认后才落库并推 issue/patch,
// 所以这里不做乐观更新。
const clearBrainstormSession = useCallback((issueNumber: number): void => {
postMessage({ type: 'issue/clear-brainstorm-session', issueNumber })
}, [])
const clearSession = useCallback(
(issueNumber: number, kind: 'brainstorm' | 'implement' | 'review' | 'test' | 'prototype'): void => {
postMessage({ type: 'issue/clear-session', issueNumber, kind })
},
[],
)
const updateIssueTestProfilePath = useCallback((issueNumber: number, testProfilePath: string): void => {
// Optimistically update local state so the dropdown reflects immediately
@@ -1039,5 +1042,5 @@ export function useIssues(): UseIssuesResult {
return cleanup
}, [clearPrDiffSummaryRunning, markHandoffRunning])
return { state, settings, avatars, isSessionTabPending, globalAutoReview, youtrackConfigured, importYouTrack, toasts, profiles, setIssues, refresh, refreshing, scope, toggleScope, saveSettings, listYouTrackProjects, youtrackProjects, dismissSettings, requestEditAuth, createIssue, dismissToast, openUrl, resumeSession, resumeReviewSession, startReviewSession, startTestSession, resumeTestSession, startPrototypeSession, resumePrototypeSession, focusSession, openFile, openPrototypeInFigma, implement, generatePrDiffSummary, isPrDiffSummaryRunning, openPr, openWorktree, deleteWorktree, mergeBranch, deleteIssue, closeIssue, closeSessionTab, continueBrainstorm, changeColumn, setDependency, clearDependency, updateIssueAutoReview, updateIssueProfilePath, updateIssueBrainstormProfilePath, clearBrainstormSession, updateIssueTestProfilePath, updateIssuePrototypeProfilePath, logs, fetchLogs, clearLogs, pendingSelectId, clearPendingSelect, commitRunning, runCommit, hasChanges, release, runRelease, cherryPick, dismissCherryPick, confirmCherryPick, envLocked, envFileCount, envLockRunning, envLockTitle, toggleEnvLock, me, handoffUsers, requestHandoffUsers, startHandoff, acceptHandoff, isHandoffRunning }
return { state, settings, avatars, isSessionTabPending, globalAutoReview, youtrackConfigured, importYouTrack, toasts, profiles, setIssues, refresh, refreshing, scope, toggleScope, saveSettings, listYouTrackProjects, youtrackProjects, dismissSettings, requestEditAuth, createIssue, dismissToast, openUrl, resumeSession, resumeReviewSession, startReviewSession, startTestSession, resumeTestSession, startPrototypeSession, resumePrototypeSession, focusSession, openFile, openPrototypeInFigma, implement, generatePrDiffSummary, isPrDiffSummaryRunning, openPr, openWorktree, deleteWorktree, mergeBranch, deleteIssue, closeIssue, closeSessionTab, continueBrainstorm, changeColumn, setDependency, clearDependency, updateIssueAutoReview, updateIssueProfilePath, updateIssueBrainstormProfilePath, clearSession, updateIssueTestProfilePath, updateIssuePrototypeProfilePath, logs, fetchLogs, clearLogs, pendingSelectId, clearPendingSelect, commitRunning, runCommit, hasChanges, release, runRelease, cherryPick, dismissCherryPick, confirmCherryPick, envLocked, envFileCount, envLockRunning, envLockTitle, toggleEnvLock, me, handoffUsers, requestHandoffUsers, startHandoff, acceptHandoff, isHandoffRunning }
}
+1 -1
View File
@@ -254,7 +254,7 @@ export type WebviewToExtension
| { type: 'issue/update-auto-review', issueNumber: number, value: boolean }
| { type: 'issue/update-profile-path', issueNumber: number, profilePath: string }
| { type: 'issue/update-brainstorm-profile-path', issueNumber: number, brainstormProfilePath: string }
| { type: 'issue/clear-brainstorm-session', issueNumber: number }
| { type: 'issue/clear-session', issueNumber: number, kind: 'brainstorm' | 'implement' | 'review' | 'test' | 'prototype' }
| { type: 'issue/update-test-profile-path', issueNumber: number, testProfilePath: string }
| { type: 'issue/update-prototype-profile-path', issueNumber: number, prototypeProfilePath: string }
| { type: 'logs/fetch' }