feat(vscode): 历史会话可换 profile 恢复

This commit is contained in:
2026-08-25 13:06:48 +08:00
parent 1c3e66bdc7
commit 07b2bf75a0
9 changed files with 177 additions and 34 deletions
@@ -60,7 +60,7 @@ interface BottomTabsProps {
profiles: ClaudeProfile[]
onManagedSessionCreate: (profilePath: string, name?: string, prompt?: string) => void
onManagedSessionRename: (id: string, name: string) => void
onManagedSessionResume: (id: string) => void
onManagedSessionResume: (id: string, profilePath?: string) => void
onManagedSessionDelete: (id: string) => void
onManagedSessionCloseTab: (id: string) => void
importableSessions: Array<{ id: string, name: string, mtimeMs: number }>
@@ -3,8 +3,8 @@
*
* - 上方表单:选 profile(下拉,复用 useIssues 的 ClaudeProfile 列表)+ 首个提示词
* (可选,多行)+「创建」按钮 → 主进程开一个 cc 终端 tab 并捕获会话。
* - 下方列表:每行显示名字(双击行内改名,Enter 提交、Esc 取消)、profile 文件名
* 创建时间;点击行(非编辑区)→ 主进程新终端 resume;行尾删除按钮(仅移除记录)。
* - 下方列表:每行显示名字(双击行内改名,Enter 提交、Esc 取消)、行内可改 profile、
* 创建时间;点行用该 profile resume 并写回;行尾删除按钮(仅移除记录)。
*/
import type { ClaudeProfile } from '../hooks/useIssues'
@@ -17,7 +17,7 @@ interface ManagedSessionsPanelProps {
profiles: ClaudeProfile[]
onCreate: (profilePath: string, name?: string, prompt?: string) => void
onRename: (id: string, name: string) => void
onResume: (id: string) => void
onResume: (id: string, profilePath?: string) => void
onDelete: (id: string) => void
onCloseTab: (id: string) => void
importableSessions: Array<{ id: string, name: string, mtimeMs: number }>
@@ -33,6 +33,16 @@ function basename(p?: string): string {
return idx >= 0 ? p.slice(idx + 1) : p
}
function matchStoredProfile(stored: string | undefined, profiles: ClaudeProfile[]): string {
if (!stored)
return ''
if (profiles.some(p => p.path === stored))
return stored
const storedBase = basename(stored)
const fuzzy = profiles.find(p => basename(p.path) === storedBase || p.name === storedBase)
return fuzzy?.path ?? stored
}
function formatTime(ts: number): string {
if (!ts)
return ''
@@ -246,6 +256,7 @@ export function ManagedSessionsPanel({
<SessionRow
key={s.id}
session={s}
profiles={profiles}
onRename={onRename}
onResume={onResume}
onDelete={onDelete}
@@ -261,16 +272,19 @@ export function ManagedSessionsPanel({
interface SessionRowProps {
session: ManagedSession
profiles: ClaudeProfile[]
onRename: (id: string, name: string) => void
onResume: (id: string) => void
onResume: (id: string, profilePath?: string) => void
onDelete: (id: string) => void
onCloseTab: (id: string) => void
}
function SessionRow({ session, onRename, onResume, onDelete, onCloseTab }: SessionRowProps) {
function SessionRow({ session, profiles, onRename, onResume, onDelete, onCloseTab }: SessionRowProps) {
const [editing, setEditing] = useState(false)
const [draft, setDraft] = useState(session.name)
const [userPicked, setUserPicked] = useState<string | undefined>(undefined)
const inputRef = useRef<HTMLInputElement | null>(null)
const pickedProfile = userPicked ?? matchStoredProfile(session.profilePath, profiles)
useLayoutEffect(() => {
if (!editing)
@@ -282,6 +296,10 @@ function SessionRow({ session, onRename, onResume, onDelete, onCloseTab }: Sessi
el.select()
}, [editing])
useEffect(() => {
setUserPicked(undefined)
}, [session.profilePath])
const beginEdit = useCallback((): void => {
setDraft(session.name)
setEditing(true)
@@ -299,8 +317,12 @@ function SessionRow({ session, onRename, onResume, onDelete, onCloseTab }: Sessi
setDraft(session.name)
}, [session.name])
const profileName = basename(session.profilePath)
const resume = useCallback((): void => {
onResume(session.id, pickedProfile || session.profilePath)
}, [onResume, session.id, session.profilePath, pickedProfile])
const created = formatTime(session.createdAt)
const storedMissing = !profiles.some(p => p.path === pickedProfile)
return (
<li
@@ -308,7 +330,7 @@ function SessionRow({ session, onRename, onResume, onDelete, onCloseTab }: Sessi
>
<button
type="button"
onClick={() => onResume(session.id)}
onClick={resume}
title="点击恢复会话(新终端 resume"
className="grid size-6 shrink-0 place-items-center rounded text-[var(--vscode-descriptionForeground)] hover:bg-[var(--vscode-toolbar-hoverBackground,var(--vscode-list-hoverBackground))] hover:text-[var(--vscode-foreground)]"
aria-label="恢复会话"
@@ -320,7 +342,7 @@ function SessionRow({ session, onRename, onResume, onDelete, onCloseTab }: Sessi
className="min-w-0 flex-1 cursor-pointer"
onClick={() => {
if (!editing)
onResume(session.id)
resume()
}}
>
{editing
@@ -357,8 +379,32 @@ function SessionRow({ session, onRename, onResume, onDelete, onCloseTab }: Sessi
>
{session.name || <span className="opacity-40">()</span>}
</span>
<span className="truncate text-[10px] text-[var(--vscode-descriptionForeground)]">
{[profileName, created].filter(Boolean).join(' · ')}
<span className="flex min-w-0 items-center text-[10px] text-[var(--vscode-descriptionForeground)]">
{profiles.length === 0
? (
<span className="truncate">{basename(session.profilePath)}</span>
)
: (
<select
value={pickedProfile}
title="换 profile 后点行打开;已打开的终端下次才生效"
onClick={e => e.stopPropagation()}
onMouseDown={e => e.stopPropagation()}
onChange={(e) => {
e.stopPropagation()
setUserPicked(e.target.value)
}}
className="min-w-0 bg-transparent py-0 pr-1 text-[10px] text-[var(--vscode-descriptionForeground)] outline-none focus:ring-1 focus:ring-inset focus:ring-[var(--vscode-focusBorder)]"
>
{storedMissing && (
<option value={pickedProfile}>{basename(pickedProfile) || '(未设置)'}</option>
)}
{profiles.map(p => (
<option key={p.path} value={p.path}>{p.name}</option>
))}
</select>
)}
{created ? <span className="shrink-0">{(profiles.length > 0 || Boolean(basename(session.profilePath))) ? ' · ' : ''}{created}</span> : null}
</span>
</div>
)}
@@ -22,7 +22,7 @@ export interface UseManagedSessionsResult {
managedSessions: ManagedSessionsData
createManagedSession: (profilePath: string, name?: string, prompt?: string) => void
renameManagedSession: (id: string, name: string) => void
resumeManagedSession: (id: string) => void
resumeManagedSession: (id: string, profilePath?: string) => void
deleteManagedSession: (id: string) => void
closeManagedSessionTab: (id: string) => void
listImportableNamedSessions: () => void
@@ -57,8 +57,8 @@ export function useManagedSessions(): UseManagedSessionsResult {
postMessage({ type: 'managed-sessions/rename', sessionId: id, name })
}, [])
const resumeManagedSession = useCallback((id: string): void => {
postMessage({ type: 'managed-sessions/resume', sessionId: id })
const resumeManagedSession = useCallback((id: string, profilePath?: string): void => {
postMessage({ type: 'managed-sessions/resume', sessionId: id, profilePath })
}, [])
const deleteManagedSession = useCallback((id: string): void => {
+1 -1
View File
@@ -206,7 +206,7 @@ export type WebviewToExtension
| { type: 'managed-sessions/get' }
| { type: 'managed-sessions/create', profilePath: string, name?: string, prompt?: string }
| { type: 'managed-sessions/rename', sessionId: string, name: string }
| { type: 'managed-sessions/resume', sessionId: string }
| { type: 'managed-sessions/resume', sessionId: string, profilePath?: string }
| { type: 'managed-sessions/delete', sessionId: string }
| { type: 'managed-sessions/close-tab', sessionId: string }
| { type: 'managed-sessions/list-importable' }