diff --git a/vscode/src/panel/KanbanPanel.ts b/vscode/src/panel/KanbanPanel.ts index 9235b95..1e98d32 100644 --- a/vscode/src/panel/KanbanPanel.ts +++ b/vscode/src/panel/KanbanPanel.ts @@ -565,8 +565,8 @@ export class KanbanWebviewPanel { void issues.handleUpdateBrainstormProfilePath(this, msg.issueNumber, msg.brainstormProfilePath) return } - if (msg.type === 'issue/clear-brainstorm-session') { - void issues.handleClearBrainstormSession(this, msg.issueNumber) + if (msg.type === 'issue/clear-session') { + void issues.handleClearSession(this, msg.issueNumber, msg.kind) return } if (msg.type === 'issue/update-test-profile-path') { diff --git a/vscode/src/panel/handlers/issues.ts b/vscode/src/panel/handlers/issues.ts index 61e7d4f..05c028a 100644 --- a/vscode/src/panel/handlers/issues.ts +++ b/vscode/src/panel/handlers/issues.ts @@ -1,6 +1,7 @@ import type { IssueColumn } from '../../gitea/types' import type { LockedReason } from '../issueLock' import type { KanbanWebviewPanel } from '../KanbanPanel' +import type { SessionTabKind } from './terminals' import { Buffer } from 'node:buffer' import { randomUUID } from 'node:crypto' import * as fs from 'node:fs' @@ -39,6 +40,7 @@ import { pickRandomIssueColor, themeColorIdToIconUri } from '../issueColor' import { evaluateLockedReason } from '../issueLock' import { makeNonce } from '../KanbanPanel' import { cleanupFeatureBranch } from './branchCleanup' +import { clearSessionConfirmText, clearSessionDoneText, SESSION_KINDS } from './sessionKinds' import * as sessions from './sessions' import { canResetToTodo, todoResetStateExtra, todoResetUiPatch } from './todoReset' @@ -1971,17 +1973,20 @@ export async function handleUpdateProfilePath(panel: KanbanWebviewPanel, issueNu } /** - * 清掉工单记的头脑风暴会话 id,下次点 ▶ 续聊就新开一个会话。 + * 清掉工单记的某种会话 id,之后该行可以新起一个会话。 * - * 用处:旧会话的历史被 `claude --resume` 原样带回来(里面可能还留着几个版本 - * 之前的提示词和它报的错),想从干净的上下文重新聊需求时用。会话文件本身不动, - * 手上有 id 仍可用 `claude --resume ` 找回。 + * 用处:会话 id 是别的机器上开的、本机没有会话文件,resume 不了又挡着新起; + * 或者旧会话历史太杂(头脑风暴里还留着几个版本之前的提示词和报错),想从干净 + * 的上下文重来。只写本机记录('' 墓碑盖住共享值),别的机器不受影响;会话文件 + * 本身不动,手上有 id 仍可 resume 找回。 */ -export async function handleClearBrainstormSession(panel: KanbanWebviewPanel, issueNumber: number): Promise { +export async function handleClearSession(panel: KanbanWebviewPanel, issueNumber: number, kind: SessionTabKind): Promise { + const { field, role } = SESSION_KINDS[kind] let previousValue: string | undefined try { const existingState = await panel.readIssueState(issueNumber) - previousValue = typeof existingState.sessionId === 'string' ? existingState.sessionId : undefined + const value = existingState[field] + previousValue = typeof value === 'string' && value.length > 0 ? value : undefined } catch { previousValue = undefined @@ -1992,14 +1997,14 @@ export async function handleClearBrainstormSession(panel: KanbanWebviewPanel, is type: 'toast/show', id: makeNonce(), level: 'info', - message: `工单 #${issueNumber} 本来就没有头脑风暴会话`, + message: `工单 #${issueNumber} 本来就没有${role}会话`, dismissOnTimer: 4000, }) return } const choice = await window.showWarningMessage( - `确定清除工单 #${issueNumber} 的头脑风暴会话 id(${previousValue})?下次续聊会新开一个会话,旧会话记录还在,可用 claude --resume 找回。`, + clearSessionConfirmText(issueNumber, kind, previousValue), { modal: true }, '清除', ) @@ -2007,21 +2012,21 @@ export async function handleClearBrainstormSession(panel: KanbanWebviewPanel, is return try { - await panel.mergeIssueState(issueNumber, { sessionId: '' }) + await panel.mergeIssueState(issueNumber, { [field]: '' }) } catch (err) { const message = err instanceof Error ? err.message : String(err) logger.add({ level: 'error', source: 'panel', - message: `清除 sessionId 失败 (issue #${issueNumber})`, + message: `清除 ${field} 失败 (issue #${issueNumber})`, details: message, }) panel.postMessage({ type: 'toast/show', id: makeNonce(), level: 'error', - message: `清除工单 #${issueNumber} 头脑风暴会话失败: ${message}`, + message: `清除工单 #${issueNumber} ${role}会话失败: ${message}`, dismissOnTimer: 6000, }) return @@ -2030,13 +2035,13 @@ export async function handleClearBrainstormSession(panel: KanbanWebviewPanel, is panel.postMessage({ type: 'issue/patch', issueNumber, - patch: { sessionId: null }, + patch: { [field]: null }, }) panel.postMessage({ type: 'toast/show', id: makeNonce(), level: 'success', - message: `工单 #${issueNumber} 头脑风暴会话已清除,下次续聊将新开会话`, + message: clearSessionDoneText(issueNumber, kind), dismissOnTimer: 5000, }) } diff --git a/vscode/src/panel/handlers/sessionKinds.test.ts b/vscode/src/panel/handlers/sessionKinds.test.ts new file mode 100644 index 0000000..69e71e8 --- /dev/null +++ b/vscode/src/panel/handlers/sessionKinds.test.ts @@ -0,0 +1,32 @@ +import { describe, expect, it } from 'vitest' +import { clearSessionConfirmText, clearSessionDoneText, SESSION_KINDS } from './sessionKinds' + +describe('会话种类表', () => { + it('五种会话各自对应 state 里的会话 id 字段,互不重复', () => { + expect(SESSION_KINDS.brainstorm.field).toBe('sessionId') + expect(SESSION_KINDS.implement.field).toBe('implementSessionId') + expect(SESSION_KINDS.review.field).toBe('reviewSessionId') + expect(SESSION_KINDS.test.field).toBe('testSessionId') + expect(SESSION_KINDS.prototype.field).toBe('prototypeSessionId') + const fields = Object.values(SESSION_KINDS).map(k => k.field) + expect(new Set(fields).size).toBe(fields.length) + }) +}) + +describe('清除会话 id 的提示文案', () => { + it('确认框:角色 + id + 只清本机,末尾接该种会话的后续提示', () => { + expect(clearSessionConfirmText(12, 'brainstorm', 'abc')).toBe( + '确定清除工单 #12 的头脑风暴会话 id(abc)?只清本机记录,别的机器不受影响;会话文件不删。下次续聊会新开会话。', + ) + expect(clearSessionConfirmText(12, 'implement', 'abc')).toContain('重新实施会删掉远程 feature 分支') + expect(clearSessionConfirmText(12, 'test', 'abc')).toBe( + '确定清除工单 #12 的测试会话 id(abc)?只清本机记录,别的机器不受影响;会话文件不删。清除后可点 ▶ 新起一个。', + ) + }) + + it('成功 toast:简短复述后续提示', () => { + expect(clearSessionDoneText(12, 'brainstorm')).toBe('工单 #12 的头脑风暴会话 id 已清除,下次续聊将新开会话') + expect(clearSessionDoneText(12, 'review')).toBe('工单 #12 的审查会话 id 已清除,可点 ▶ 新起一个') + expect(clearSessionDoneText(12, 'implement')).toContain('会删掉远程 feature 分支') + }) +}) diff --git a/vscode/src/panel/handlers/sessionKinds.ts b/vscode/src/panel/handlers/sessionKinds.ts new file mode 100644 index 0000000..e7294c6 --- /dev/null +++ b/vscode/src/panel/handlers/sessionKinds.ts @@ -0,0 +1,51 @@ +import type { SessionTabKind } from './terminals' + +/** 工单 state 里存各种会话 id 的字段。 */ +export type SessionIdField = 'sessionId' | 'implementSessionId' | 'reviewSessionId' | 'testSessionId' | 'prototypeSessionId' + +/** + * 每种会话对应的 state 字段、中文角色名,以及清除该会话 id 后的后续提示: + * confirmHint 进确认框,doneHint 进成功 toast。 + */ +export const SESSION_KINDS: Record = { + brainstorm: { + field: 'sessionId', + role: '头脑风暴', + confirmHint: '下次续聊会新开会话', + doneHint: '下次续聊将新开会话', + }, + implement: { + field: 'implementSessionId', + role: '实施', + confirmHint: '清除后可点 ▶ 重新实施,注意重新实施会删掉远程 feature 分支,别的机器上的实施进度会丢', + doneHint: '可点 ▶ 重新实施(会删掉远程 feature 分支)', + }, + review: { + field: 'reviewSessionId', + role: '审查', + confirmHint: '清除后可点 ▶ 新起一个', + doneHint: '可点 ▶ 新起一个', + }, + test: { + field: 'testSessionId', + role: '测试', + confirmHint: '清除后可点 ▶ 新起一个', + doneHint: '可点 ▶ 新起一个', + }, + prototype: { + field: 'prototypeSessionId', + role: '原型', + confirmHint: '清除后可点 ▶ 新起一个', + doneHint: '可点 ▶ 新起一个', + }, +} + +export function clearSessionConfirmText(issueNumber: number, kind: SessionTabKind, sessionId: string): string { + const { role, confirmHint } = SESSION_KINDS[kind] + return `确定清除工单 #${issueNumber} 的${role}会话 id(${sessionId})?只清本机记录,别的机器不受影响;会话文件不删。${confirmHint}。` +} + +export function clearSessionDoneText(issueNumber: number, kind: SessionTabKind): string { + const { role, doneHint } = SESSION_KINDS[kind] + return `工单 #${issueNumber} 的${role}会话 id 已清除,${doneHint}` +} diff --git a/vscode/src/panel/messages.ts b/vscode/src/panel/messages.ts index a72d6d0..ef615f6 100644 --- a/vscode/src/panel/messages.ts +++ b/vscode/src/panel/messages.ts @@ -223,7 +223,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' } diff --git a/vscode/src/panel/readOnlyGuard.test.ts b/vscode/src/panel/readOnlyGuard.test.ts index 066ebb9..462d506 100644 --- a/vscode/src/panel/readOnlyGuard.test.ts +++ b/vscode/src/panel/readOnlyGuard.test.ts @@ -8,6 +8,7 @@ describe('isOwnerOnlyMessage', () => { expect(isOwnerOnlyMessage('issue/close')).toBe(true) expect(isOwnerOnlyMessage('dependency/set')).toBe(true) expect(isOwnerOnlyMessage('issue/update-auto-review')).toBe(true) + expect(isOwnerOnlyMessage('issue/clear-session')).toBe(true) }) it('在本机替别人跑会话的消息要门禁', () => { diff --git a/vscode/src/panel/readOnlyGuard.ts b/vscode/src/panel/readOnlyGuard.ts index 2aa3a2e..c872f84 100644 --- a/vscode/src/panel/readOnlyGuard.ts +++ b/vscode/src/panel/readOnlyGuard.ts @@ -29,7 +29,7 @@ export const OWNER_ONLY_MESSAGE_TYPES: ReadonlySet = new Set([ 'issue/update-auto-review', 'issue/update-profile-path', 'issue/update-brainstorm-profile-path', - 'issue/clear-brainstorm-session', + 'issue/clear-session', 'issue/update-test-profile-path', 'issue/update-prototype-profile-path', 'dependency/set', diff --git a/vscode/webview-ui/src/App.tsx b/vscode/webview-ui/src/App.tsx index 1682da0..21a592c 100644 --- a/vscode/webview-ui/src/App.tsx +++ b/vscode/webview-ui/src/App.tsx @@ -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} diff --git a/vscode/webview-ui/src/components/BottomTabs.tsx b/vscode/webview-ui/src/components/BottomTabs.tsx index efee702..48bc9f2 100644 --- a/vscode/webview-ui/src/components/BottomTabs.tsx +++ b/vscode/webview-ui/src/components/BottomTabs.tsx @@ -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} diff --git a/vscode/webview-ui/src/components/IssueDetailPanel.tsx b/vscode/webview-ui/src/components/IssueDetailPanel.tsx index db11eed..476d973 100644 --- a/vscode/webview-ui/src/components/IssueDetailPanel.tsx +++ b/vscode/webview-ui/src/components/IssueDetailPanel.tsx @@ -115,7 +115,8 @@ interface IssueDetailPanelProps { * 打进去,关了就 `claude --resume ` 带着提示词恢复,没会话就新开一个。 */ 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: , + title: '清除会话 id:只清本机记录,之后可新起一个', + onClick: () => onClearSession(issueNumber, kind), + }) + const schema = useMemo( () => [ { @@ -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: , - 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: , 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: , secondaryActions, } } @@ -384,13 +387,16 @@ export function IssueDetailPanel({ return onResumeReviewSession(v, issue.number, issue?.worktreePath) }, - secondaryActions: issue.reviewTabOpen - ? [{ - icon: , - title: '关闭审查 tab', - onClick: () => onCloseSessionTab(issue.number, 'review'), - }] - : [], + secondaryActions: [ + ...(issue.reviewTabOpen + ? [{ + icon: , + 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: , - title: locked ? lockTitle : '启动审查会话', - disabled: locked, - onClick: () => onStartReviewSession(issue.number), - }] - : [], + secondaryActions: [ + ...(issue?.pr + ? [{ + icon: , + 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: , 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: , 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], ) // 同事的工单只读:属性表统一消毒,顶部只留查看类按钮。 diff --git a/vscode/webview-ui/src/hooks/useIssues.ts b/vscode/webview-ui/src/hooks/useIssues.ts index 276301a..0daa369 100644 --- a/vscode/webview-ui/src/hooks/useIssues.ts +++ b/vscode/webview-ui/src/hooks/useIssues.ts @@ -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 } } diff --git a/vscode/webview-ui/src/lib/messages.ts b/vscode/webview-ui/src/lib/messages.ts index 80d1b01..314966d 100644 --- a/vscode/webview-ui/src/lib/messages.ts +++ b/vscode/webview-ui/src/lib/messages.ts @@ -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' }