From 7b4f737ce5a23dc7c9b6d7a80e909de41c8d3343 Mon Sep 17 00:00:00 2001 From: cruldra Date: Thu, 9 Jul 2026 08:41:58 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat(vscode):=20=E5=86=B2=E7=AA=81?= =?UTF-8?q?=E8=A7=A3=E5=86=B3=E4=BC=9A=E8=AF=9D=20profile=20=E5=8F=AF?= =?UTF-8?q?=E5=9C=A8=E8=AE=BE=E7=BD=AE=E9=9D=A2=E6=9D=BF=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- vscode/src/panel/KanbanPanel.ts | 10 +++-- vscode/src/panel/handlers/issues.ts | 4 -- vscode/src/panel/handlers/sessions.ts | 21 +++++----- vscode/src/panel/handlers/settings.ts | 6 +++ vscode/src/panel/messages.ts | 2 + vscode/src/settings/store.ts | 12 ++++++ vscode/webview-ui/src/App.tsx | 2 + .../src/components/SettingsModal.tsx | 39 +++++++++++++++++++ vscode/webview-ui/src/hooks/useIssues.ts | 4 ++ vscode/webview-ui/src/lib/messages.ts | 2 + 10 files changed, 85 insertions(+), 17 deletions(-) diff --git a/vscode/src/panel/KanbanPanel.ts b/vscode/src/panel/KanbanPanel.ts index 4ec1a99..0f28e19 100644 --- a/vscode/src/panel/KanbanPanel.ts +++ b/vscode/src/panel/KanbanPanel.ts @@ -854,6 +854,7 @@ export class KanbanWebviewPanel { worktreePreRemoveScript: s.worktreePreRemoveScript, implTabPreCreateScript: s.implTabPreCreateScript, implTabPostCloseScript: s.implTabPostCloseScript, + conflictResolutionProfilePath: s.conflictResolutionProfilePath, }) return } @@ -916,6 +917,7 @@ export class KanbanWebviewPanel { worktreePreRemoveScript: s.worktreePreRemoveScript, implTabPreCreateScript: s.implTabPreCreateScript, implTabPostCloseScript: s.implTabPostCloseScript, + conflictResolutionProfilePath: s.conflictResolutionProfilePath, }) return } @@ -926,13 +928,15 @@ export class KanbanWebviewPanel { } /** - * Pick the profile.json that the *implement-class* cc sessions - * (handleImplement / handleResumeSession when sessionKind === 'implement' / - * startConflictResolution) should launch with. Priority: + * Pick the profile.json that implement-class cc sessions + * (handleImplement / handleResumeSession when sessionKind === 'implement') + * should launch with. Priority: * 1. The per-issue `profilePath` recorded in state JSON (preserves the * brainstorm-time choice, overridable from the issue detail panel). * 2. The hard-coded `DEFAULT_PROFILE_PATH` fallback. * + * Conflict-resolution sessions use the global + * `settings.conflictResolutionProfilePath` instead of this helper. * Brainstorm sessions deliberately don't go through this helper — they * keep the legacy `profilePath || DEFAULT_PROFILE_PATH` so creators can * still pick a profile per issue at brainstorm time. diff --git a/vscode/src/panel/handlers/issues.ts b/vscode/src/panel/handlers/issues.ts index 1f9804b..3a6623b 100644 --- a/vscode/src/panel/handlers/issues.ts +++ b/vscode/src/panel/handlers/issues.ts @@ -107,7 +107,6 @@ export async function handleColumnChange(panel: KanbanWebviewPanel, issueNumber: let implementSessionId: string | undefined let testSessionId: string | undefined let featureBranch: string | undefined - let profilePath: string | undefined try { const comments = await listIssueComments({ host: remote.host, @@ -139,8 +138,6 @@ export async function handleColumnChange(panel: KanbanWebviewPanel, issueNumber: testSessionId = obj.testSessionId if (typeof obj.branch === 'string' && obj.branch.length > 0) featureBranch = obj.branch - if (typeof obj.profilePath === 'string' && obj.profilePath.length > 0) - profilePath = obj.profilePath } } catch { @@ -340,7 +337,6 @@ export async function handleColumnChange(panel: KanbanWebviewPanel, issueNumber: workspaceRoot, featureBranch, worktreePath, - profilePath, }) } else { diff --git a/vscode/src/panel/handlers/sessions.ts b/vscode/src/panel/handlers/sessions.ts index 63b7198..b05f85e 100644 --- a/vscode/src/panel/handlers/sessions.ts +++ b/vscode/src/panel/handlers/sessions.ts @@ -67,8 +67,8 @@ export async function handleResumeSession(panel: KanbanWebviewPanel, sessionId: existing.show(false) return } - // 实施 resume 走工单级 profilePath > 默认 的优先级(同 handleImplement / - // startConflictResolution);头脑风暴 resume 保持现状,只看工单级 + 默认。 + // 实施 resume 走工单级 profilePath > 默认 的优先级(同 handleImplement); + // 头脑风暴 resume 保持现状,只看工单级 + 默认。 const effectiveProfilePath = sessionKind === 'implement' ? panel.resolveImplementProfilePath(profilePath) @@ -1396,9 +1396,8 @@ export async function startConflictResolution(panel: KanbanWebviewPanel, opts: { workspaceRoot: string featureBranch: string worktreePath: string - profilePath?: string }): Promise { - const { issueNumber, prIndex, workspaceRoot, featureBranch, worktreePath, profilePath } = opts + const { issueNumber, prIndex, workspaceRoot, featureBranch, worktreePath } = opts const settings = getSettings(panel.context) const devBranch = settings.devBranch || 'main' @@ -1553,9 +1552,9 @@ export async function startConflictResolution(panel: KanbanWebviewPanel, opts: { return } terminal.show(false) - // 与 handleImplement 保持一致:走工单级 profilePath > 默认 fallback 的优先级, - // 让冲突解决会话和实施会话用同一份 profile。 - const effectiveProfilePath = panel.resolveImplementProfilePath(profilePath) + // 冲突解决只用全局设置 conflictResolutionProfilePath;空串 = DEFAULT_PROFILE_PATH。 + const effectiveProfilePath + = settings.conflictResolutionProfilePath.trim() || DEFAULT_PROFILE_PATH if (effectiveProfilePath.includes('\'')) { logger.add({ level: 'error', @@ -1590,13 +1589,15 @@ export async function startConflictResolution(panel: KanbanWebviewPanel, opts: { } /** - * Pick the profile.json that the *implement-class* cc sessions - * (handleImplement / handleResumeSession when sessionKind === 'implement' / - * startConflictResolution) should launch with. Priority: + * Pick the profile.json that implement-class cc sessions + * (handleImplement / handleResumeSession when sessionKind === 'implement') + * should launch with. Priority: * 1. The per-issue `profilePath` recorded in state JSON (preserves the * brainstorm-time choice, overridable from the issue detail panel). * 2. The hard-coded `DEFAULT_PROFILE_PATH` fallback. * + * Conflict-resolution sessions use the global + * `settings.conflictResolutionProfilePath` instead of this helper. * Brainstorm sessions deliberately don't go through this helper — they * keep the legacy `profilePath || DEFAULT_PROFILE_PATH` so creators can * still pick a profile per issue at brainstorm time. diff --git a/vscode/src/panel/handlers/settings.ts b/vscode/src/panel/handlers/settings.ts index f64987e..88bd144 100644 --- a/vscode/src/panel/handlers/settings.ts +++ b/vscode/src/panel/handlers/settings.ts @@ -32,6 +32,7 @@ export async function handleSettingsSave(panel: KanbanWebviewPanel, payload: { worktreePreRemoveScript: string implTabPreCreateScript: string implTabPostCloseScript: string + conflictResolutionProfilePath: string youtrackBaseUrl: string youtrackProjectShortName: string youtrackCloseCommand: string @@ -58,6 +59,8 @@ export async function handleSettingsSave(panel: KanbanWebviewPanel, payload: { const trimmedPreRemove = payload.worktreePreRemoveScript.trim() const trimmedImplPre = payload.implTabPreCreateScript.trim() const trimmedImplPost = payload.implTabPostCloseScript.trim() + // conflict-resolution profile: '' is meaningful (= DEFAULT_PROFILE_PATH). + const trimmedConflictProfile = payload.conflictResolutionProfilePath.trim() const prev = getSettings(panel.context) // Capture the previous token *for this host* before overwriting it, so // we can decide below whether the kanban needs a re-fetch. (Only host @@ -87,6 +90,7 @@ export async function handleSettingsSave(panel: KanbanWebviewPanel, payload: { worktreePreRemoveScript: trimmedPreRemove, implTabPreCreateScript: trimmedImplPre, implTabPostCloseScript: trimmedImplPost, + conflictResolutionProfilePath: trimmedConflictProfile, }) return } @@ -108,6 +112,7 @@ export async function handleSettingsSave(panel: KanbanWebviewPanel, payload: { worktreePreRemoveScript: trimmedPreRemove, implTabPreCreateScript: trimmedImplPre, implTabPostCloseScript: trimmedImplPost, + conflictResolutionProfilePath: trimmedConflictProfile, youtrackBaseUrl: trimmedYtBase, youtrackProjectShortName: trimmedYtProject, youtrackCloseCommand: trimmedYtClose, @@ -190,6 +195,7 @@ export async function handleEditSettingsRequest(panel: KanbanWebviewPanel): Prom worktreePreRemoveScript: s.worktreePreRemoveScript, implTabPreCreateScript: s.implTabPreCreateScript, implTabPostCloseScript: s.implTabPostCloseScript, + conflictResolutionProfilePath: s.conflictResolutionProfilePath, youtrackBaseUrl: s.youtrackBaseUrl, youtrackProjectShortName: s.youtrackProjectShortName, youtrackCloseCommand: s.youtrackCloseCommand, diff --git a/vscode/src/panel/messages.ts b/vscode/src/panel/messages.ts index 3a59cc5..c2d04ab 100644 --- a/vscode/src/panel/messages.ts +++ b/vscode/src/panel/messages.ts @@ -66,6 +66,7 @@ export type ExtensionToWebview worktreePreRemoveScript: string implTabPreCreateScript: string implTabPostCloseScript: string + conflictResolutionProfilePath: string youtrackBaseUrl?: string youtrackProjectShortName?: string youtrackCloseCommand?: string @@ -127,6 +128,7 @@ export type WebviewToExtension worktreePreRemoveScript: string implTabPreCreateScript: string implTabPostCloseScript: string + conflictResolutionProfilePath: string youtrackBaseUrl: string youtrackProjectShortName: string youtrackCloseCommand: string diff --git a/vscode/src/settings/store.ts b/vscode/src/settings/store.ts index d852937..d832d9a 100644 --- a/vscode/src/settings/store.ts +++ b/vscode/src/settings/store.ts @@ -164,6 +164,11 @@ export interface Settings { * extension finds the project's first `isResolved` state value and uses that. */ youtrackCloseCommand: string + /** + * Claude settings profile path for the conflict-resolution cc session + * (drag to 完成 when PR merge conflicts). Empty = DEFAULT_PROFILE_PATH. + */ + conflictResolutionProfilePath: string } export const SETTINGS_KEY = 'superpowers.settings' @@ -188,6 +193,7 @@ function defaults(ctx: ExtensionContext): Settings { youtrackBaseUrl: '', youtrackProjectShortName: '', youtrackCloseCommand: '', + conflictResolutionProfilePath: '', } } @@ -267,6 +273,11 @@ export function getSettings(ctx: ExtensionContext): Settings { const youtrackCloseCommand = typeof stored.youtrackCloseCommand === 'string' ? stored.youtrackCloseCommand : base.youtrackCloseCommand + // conflict-resolution profile: '' is meaningful (= DEFAULT_PROFILE_PATH at + // launch time), so don't coerce. Non-string legacy values collapse to ''. + const conflictResolutionProfilePath = typeof stored.conflictResolutionProfilePath === 'string' + ? stored.conflictResolutionProfilePath + : base.conflictResolutionProfilePath return { webhookPort, @@ -287,6 +298,7 @@ export function getSettings(ctx: ExtensionContext): Settings { youtrackBaseUrl, youtrackProjectShortName, youtrackCloseCommand, + conflictResolutionProfilePath, } } diff --git a/vscode/webview-ui/src/App.tsx b/vscode/webview-ui/src/App.tsx index 4f6f5fd..b0b48a1 100644 --- a/vscode/webview-ui/src/App.tsx +++ b/vscode/webview-ui/src/App.tsx @@ -452,10 +452,12 @@ export function App() { initialWorktreePreRemoveScript={settings?.worktreePreRemoveScript ?? ''} initialImplTabPreCreateScript={settings?.implTabPreCreateScript ?? ''} initialImplTabPostCloseScript={settings?.implTabPostCloseScript ?? ''} + initialConflictResolutionProfilePath={settings?.conflictResolutionProfilePath ?? ''} initialYoutrackBaseUrl={settings?.youtrackBaseUrl ?? ''} initialYoutrackProjectShortName={settings?.youtrackProjectShortName ?? ''} initialYoutrackCloseCommand={settings?.youtrackCloseCommand ?? ''} initialYoutrackTokenSaved={settings?.youtrackTokenSaved ?? false} + profiles={profiles} youtrackProjects={youtrackProjects} onListYouTrackProjects={listYouTrackProjects} onSubmit={saveSettings} diff --git a/vscode/webview-ui/src/components/SettingsModal.tsx b/vscode/webview-ui/src/components/SettingsModal.tsx index 1486d3e..771a648 100644 --- a/vscode/webview-ui/src/components/SettingsModal.tsx +++ b/vscode/webview-ui/src/components/SettingsModal.tsx @@ -10,6 +10,7 @@ import type { ReactElement } from 'react' import { useEffect, useState } from 'react' import { X } from 'lucide-react' +import type { ClaudeProfile } from '../hooks/useIssues' type GroupKey = 'auth' | 'network' | 'youtrack' | 'prompts' | 'hooks' @@ -30,6 +31,7 @@ interface SubmitValues { worktreePreRemoveScript: string implTabPreCreateScript: string implTabPostCloseScript: string + conflictResolutionProfilePath: string youtrackBaseUrl: string youtrackProjectShortName: string youtrackCloseCommand: string @@ -69,10 +71,12 @@ export interface SettingsModalProps { initialWorktreePreRemoveScript: string initialImplTabPreCreateScript: string initialImplTabPostCloseScript: string + initialConflictResolutionProfilePath: string initialYoutrackBaseUrl: string initialYoutrackProjectShortName: string initialYoutrackCloseCommand: string initialYoutrackTokenSaved: boolean + profiles: ClaudeProfile[] youtrackProjects: YouTrackProjectsState onListYouTrackProjects: (baseUrl: string, token: string) => void onSubmit: (values: SubmitValues) => void @@ -141,10 +145,12 @@ export function SettingsModal({ initialWorktreePreRemoveScript, initialImplTabPreCreateScript, initialImplTabPostCloseScript, + initialConflictResolutionProfilePath, initialYoutrackBaseUrl, initialYoutrackProjectShortName, initialYoutrackCloseCommand, initialYoutrackTokenSaved, + profiles, youtrackProjects, onListYouTrackProjects, onSubmit, @@ -167,6 +173,7 @@ export function SettingsModal({ const [worktreePreRemoveScript, setWorktreePreRemoveScript] = useState(initialWorktreePreRemoveScript) const [implTabPreCreateScript, setImplTabPreCreateScript] = useState(initialImplTabPreCreateScript) const [implTabPostCloseScript, setImplTabPostCloseScript] = useState(initialImplTabPostCloseScript) + const [conflictResolutionProfilePath, setConflictResolutionProfilePath] = useState(initialConflictResolutionProfilePath) const [youtrackBaseUrl, setYoutrackBaseUrl] = useState(initialYoutrackBaseUrl) const [youtrackProjectShortName, setYoutrackProjectShortName] = useState(initialYoutrackProjectShortName) const [youtrackCloseCommand, setYoutrackCloseCommand] = useState(initialYoutrackCloseCommand) @@ -189,10 +196,14 @@ export function SettingsModal({ setReviewPrompt(initialReviewPrompt) setDevBranch(initialDevBranch) setAutoBuildBranch(initialAutoBuildBranch) + setWorktreeDirectory(initialWorktreeDirectory) + setWorktreeOpenWith(initialWorktreeOpenWith) + setPycharmCommand(initialPycharmCommand) setWorktreePostCreateScript(initialWorktreePostCreateScript) setWorktreePreRemoveScript(initialWorktreePreRemoveScript) setImplTabPreCreateScript(initialImplTabPreCreateScript) setImplTabPostCloseScript(initialImplTabPostCloseScript) + setConflictResolutionProfilePath(initialConflictResolutionProfilePath) setYoutrackBaseUrl(initialYoutrackBaseUrl) setYoutrackProjectShortName(initialYoutrackProjectShortName) setYoutrackCloseCommand(initialYoutrackCloseCommand) @@ -274,6 +285,8 @@ export function SettingsModal({ worktreePreRemoveScript: worktreePreRemoveScript.trim(), implTabPreCreateScript: implTabPreCreateScript.trim(), implTabPostCloseScript: implTabPostCloseScript.trim(), + // 冲突解决 profile:空串 = 用 DEFAULT_PROFILE_PATH + conflictResolutionProfilePath: conflictResolutionProfilePath.trim(), youtrackBaseUrl: youtrackBaseUrl.trim(), youtrackProjectShortName: youtrackProjectShortName.trim(), youtrackCloseCommand: youtrackCloseCommand.trim(), @@ -424,6 +437,32 @@ export function SettingsModal({ className={inputClass} /> + + + 拖到「完成」时若 PR 与 main/dev 冲突,自动开的冲突解决会话使用此 profile;留空用默认 official。 + + )} + > + + )} diff --git a/vscode/webview-ui/src/hooks/useIssues.ts b/vscode/webview-ui/src/hooks/useIssues.ts index 24a6c23..ab30ba6 100644 --- a/vscode/webview-ui/src/hooks/useIssues.ts +++ b/vscode/webview-ui/src/hooks/useIssues.ts @@ -40,6 +40,7 @@ export interface SettingsValues { worktreePreRemoveScript: string implTabPreCreateScript: string implTabPostCloseScript: string + conflictResolutionProfilePath: string youtrackBaseUrl: string youtrackProjectShortName: string youtrackCloseCommand: string @@ -65,6 +66,7 @@ export interface SettingsOverlayState { worktreePreRemoveScript: string implTabPreCreateScript: string implTabPostCloseScript: string + conflictResolutionProfilePath: string youtrackBaseUrl: string youtrackProjectShortName: string youtrackCloseCommand: string @@ -278,6 +280,7 @@ export function useIssues(): UseIssuesResult { worktreePreRemoveScript: values.worktreePreRemoveScript, implTabPreCreateScript: values.implTabPreCreateScript, implTabPostCloseScript: values.implTabPostCloseScript, + conflictResolutionProfilePath: values.conflictResolutionProfilePath, youtrackBaseUrl: values.youtrackBaseUrl, youtrackProjectShortName: values.youtrackProjectShortName, youtrackCloseCommand: values.youtrackCloseCommand, @@ -606,6 +609,7 @@ export function useIssues(): UseIssuesResult { worktreePreRemoveScript: msg.worktreePreRemoveScript, implTabPreCreateScript: msg.implTabPreCreateScript, implTabPostCloseScript: msg.implTabPostCloseScript, + conflictResolutionProfilePath: msg.conflictResolutionProfilePath, youtrackBaseUrl: msg.youtrackBaseUrl ?? '', youtrackProjectShortName: msg.youtrackProjectShortName ?? '', youtrackCloseCommand: msg.youtrackCloseCommand ?? '', diff --git a/vscode/webview-ui/src/lib/messages.ts b/vscode/webview-ui/src/lib/messages.ts index 9e96f30..4ff91bf 100644 --- a/vscode/webview-ui/src/lib/messages.ts +++ b/vscode/webview-ui/src/lib/messages.ts @@ -81,6 +81,7 @@ export type ExtensionToWebview worktreePreRemoveScript: string implTabPreCreateScript: string implTabPostCloseScript: string + conflictResolutionProfilePath: string youtrackBaseUrl?: string youtrackProjectShortName?: string youtrackCloseCommand?: string @@ -142,6 +143,7 @@ export type WebviewToExtension worktreePreRemoveScript: string implTabPreCreateScript: string implTabPostCloseScript: string + conflictResolutionProfilePath: string youtrackBaseUrl: string youtrackProjectShortName: string youtrackCloseCommand: string