✨ feat(vscode): 提交代码按钮可配置 Claude profile
This commit is contained in:
@@ -0,0 +1,113 @@
|
|||||||
|
import { accessSync, constants, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
|
||||||
|
import * as os from 'node:os'
|
||||||
|
import * as path from 'node:path'
|
||||||
|
import { afterEach, describe, expect, it } from 'vitest'
|
||||||
|
import {
|
||||||
|
DEFAULT_COMMIT_PROFILE_NAME,
|
||||||
|
resolveCommitProfileForRun,
|
||||||
|
seedCommitProfilePath,
|
||||||
|
} from './commitProfile'
|
||||||
|
|
||||||
|
const tempDirs: string[] = []
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
while (tempDirs.length > 0) {
|
||||||
|
const dir = tempDirs.pop()
|
||||||
|
if (dir)
|
||||||
|
rmSync(dir, { recursive: true, force: true })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
function tempDir(): string {
|
||||||
|
const dir = mkdtempSync(path.join(os.tmpdir(), 'commit-profile-'))
|
||||||
|
tempDirs.push(dir)
|
||||||
|
return dir
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('seedCommitProfilePath', () => {
|
||||||
|
it('key 缺失时预填 deepseek-v4-pro 的 path', () => {
|
||||||
|
const profiles = [
|
||||||
|
{ name: 'grok-4.5', path: '/p/grok-4.5.json' },
|
||||||
|
{ name: DEFAULT_COMMIT_PROFILE_NAME, path: '/p/deepseek-v4-pro.json' },
|
||||||
|
]
|
||||||
|
expect(seedCommitProfilePath(undefined, profiles)).toBe('/p/deepseek-v4-pro.json')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('key 缺失且无 deepseek 时返回空串', () => {
|
||||||
|
expect(seedCommitProfilePath(undefined, [{ name: 'grok-4.5', path: '/p/g.json' }])).toBe('')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('key 已存在为空串时尊重用户清空', () => {
|
||||||
|
expect(seedCommitProfilePath('', [{ name: DEFAULT_COMMIT_PROFILE_NAME, path: '/p/d.json' }])).toBe('')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('key 已存在为 path 时原样返回', () => {
|
||||||
|
expect(seedCommitProfilePath('/custom/x.json', [{ name: DEFAULT_COMMIT_PROFILE_NAME, path: '/p/d.json' }])).toBe('/custom/x.json')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('resolveCommitProfileForRun', () => {
|
||||||
|
it('空串 → empty', () => {
|
||||||
|
expect(resolveCommitProfileForRun(' ', p => p, () => true)).toEqual({ ok: false, reason: 'empty' })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('文件不可读 → unreadable', () => {
|
||||||
|
const dir = tempDir()
|
||||||
|
const missing = path.join(dir, 'gone.json')
|
||||||
|
const result = resolveCommitProfileForRun(
|
||||||
|
missing,
|
||||||
|
p => p,
|
||||||
|
p => {
|
||||||
|
try {
|
||||||
|
accessSync(p, constants.R_OK)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
expect(result).toEqual({ ok: false, reason: 'unreadable', profilePath: missing })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('可读 path → ok', () => {
|
||||||
|
const dir = tempDir()
|
||||||
|
const file = path.join(dir, 'deepseek-v4-pro.json')
|
||||||
|
writeFileSync(file, '{}')
|
||||||
|
const result = resolveCommitProfileForRun(
|
||||||
|
file,
|
||||||
|
p => p,
|
||||||
|
p => {
|
||||||
|
try {
|
||||||
|
accessSync(p, constants.R_OK)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
expect(result).toEqual({ ok: true, profilePath: file })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('经 resolvePath remap 后再检查可读', () => {
|
||||||
|
const dir = tempDir()
|
||||||
|
const local = path.join(dir, 'deepseek-v4-pro.json')
|
||||||
|
writeFileSync(local, '{}')
|
||||||
|
const foreign = '/Users/other/.claude/profiles/deepseek-v4-pro.json'
|
||||||
|
const result = resolveCommitProfileForRun(
|
||||||
|
foreign,
|
||||||
|
() => local,
|
||||||
|
p => {
|
||||||
|
try {
|
||||||
|
accessSync(p, constants.R_OK)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
expect(result).toEqual({ ok: true, profilePath: local })
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
/**
|
||||||
|
* 工具栏「提交代码」所用 profile 的解析与默认 seed。
|
||||||
|
* 纯逻辑:不碰 VS Code API,便于单测。
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const DEFAULT_COMMIT_PROFILE_NAME = 'deepseek-v4-pro'
|
||||||
|
|
||||||
|
export interface ProfileRef {
|
||||||
|
name: string
|
||||||
|
path: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 升级迁移:stored 里从未出现 `commitProfilePath` key 时,
|
||||||
|
* 若本机 profiles 有 deepseek-v4-pro 则预填其 path,否则 ''。
|
||||||
|
* 已出现 key(含用户主动清空的 '')一律原样返回。
|
||||||
|
*/
|
||||||
|
export function seedCommitProfilePath(
|
||||||
|
stored: string | undefined,
|
||||||
|
profiles: ProfileRef[],
|
||||||
|
): string {
|
||||||
|
if (typeof stored === 'string')
|
||||||
|
return stored
|
||||||
|
const hit = profiles.find(p => p.name === DEFAULT_COMMIT_PROFILE_NAME)
|
||||||
|
return hit?.path ?? ''
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CommitProfileResolution
|
||||||
|
= | { ok: true, profilePath: string }
|
||||||
|
| { ok: false, reason: 'empty' }
|
||||||
|
| { ok: false, reason: 'unreadable', profilePath: string }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提交前解析:trim → 空失败 → resolvePath → 可读性检查。
|
||||||
|
* resolvePath / isReadable 由调用方注入(跨机 remap / fs)。
|
||||||
|
*/
|
||||||
|
export function resolveCommitProfileForRun(
|
||||||
|
raw: string,
|
||||||
|
resolvePath: (stored: string) => string,
|
||||||
|
isReadable: (profilePath: string) => boolean,
|
||||||
|
): CommitProfileResolution {
|
||||||
|
const trimmed = raw.trim()
|
||||||
|
if (!trimmed)
|
||||||
|
return { ok: false, reason: 'empty' }
|
||||||
|
const profilePath = resolvePath(trimmed)
|
||||||
|
if (!isReadable(profilePath))
|
||||||
|
return { ok: false, reason: 'unreadable', profilePath }
|
||||||
|
return { ok: true, profilePath }
|
||||||
|
}
|
||||||
@@ -21,7 +21,7 @@ import { loadIssues } from '../gitea/issueLoader'
|
|||||||
import type { IssueRef } from '../issues/stateRouter'
|
import type { IssueRef } from '../issues/stateRouter'
|
||||||
import { closeIssueByRef, mergeIssueState, readIssueState } from '../issues/stateRouter'
|
import { closeIssueByRef, mergeIssueState, readIssueState } from '../issues/stateRouter'
|
||||||
import { logger } from '../logging/logger'
|
import { logger } from '../logging/logger'
|
||||||
import { getSettings } from '../settings/store'
|
import { getEffectiveCommitProfilePath, getSettings } from '../settings/store'
|
||||||
import { webhookCoordinator } from '../webhook/coordinator'
|
import { webhookCoordinator } from '../webhook/coordinator'
|
||||||
import { loadYouTrackIssues } from '../youtrack/issueLoader'
|
import { loadYouTrackIssues } from '../youtrack/issueLoader'
|
||||||
import * as issues from './handlers/issues'
|
import * as issues from './handlers/issues'
|
||||||
@@ -864,6 +864,7 @@ export class KanbanWebviewPanel {
|
|||||||
implTabPreCreateScript: s.implTabPreCreateScript,
|
implTabPreCreateScript: s.implTabPreCreateScript,
|
||||||
implTabPostCloseScript: s.implTabPostCloseScript,
|
implTabPostCloseScript: s.implTabPostCloseScript,
|
||||||
conflictResolutionProfilePath: s.conflictResolutionProfilePath,
|
conflictResolutionProfilePath: s.conflictResolutionProfilePath,
|
||||||
|
commitProfilePath: await getEffectiveCommitProfilePath(this.context),
|
||||||
codexModel: s.codexModel,
|
codexModel: s.codexModel,
|
||||||
codexReasoningEffort: s.codexReasoningEffort,
|
codexReasoningEffort: s.codexReasoningEffort,
|
||||||
profilesDirectory: s.profilesDirectory,
|
profilesDirectory: s.profilesDirectory,
|
||||||
@@ -930,6 +931,7 @@ export class KanbanWebviewPanel {
|
|||||||
implTabPreCreateScript: s.implTabPreCreateScript,
|
implTabPreCreateScript: s.implTabPreCreateScript,
|
||||||
implTabPostCloseScript: s.implTabPostCloseScript,
|
implTabPostCloseScript: s.implTabPostCloseScript,
|
||||||
conflictResolutionProfilePath: s.conflictResolutionProfilePath,
|
conflictResolutionProfilePath: s.conflictResolutionProfilePath,
|
||||||
|
commitProfilePath: await getEffectiveCommitProfilePath(this.context),
|
||||||
codexModel: s.codexModel,
|
codexModel: s.codexModel,
|
||||||
codexReasoningEffort: s.codexReasoningEffort,
|
codexReasoningEffort: s.codexReasoningEffort,
|
||||||
profilesDirectory: s.profilesDirectory,
|
profilesDirectory: s.profilesDirectory,
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { listClaudeProfiles, setProfilesDirOverride } from '../../cc/profiles'
|
|||||||
import { detectRepo } from '../../git/remote'
|
import { detectRepo } from '../../git/remote'
|
||||||
import { logger } from '../../logging/logger'
|
import { logger } from '../../logging/logger'
|
||||||
import { readProfiles, writeProfiles } from '../../profiles/store'
|
import { readProfiles, writeProfiles } from '../../profiles/store'
|
||||||
import { getSettings, saveSettings } from '../../settings/store'
|
import { getEffectiveCommitProfilePath, getSettings, saveSettings } from '../../settings/store'
|
||||||
import { webhookCoordinator } from '../../webhook/coordinator'
|
import { webhookCoordinator } from '../../webhook/coordinator'
|
||||||
import { youtrackHost } from '../../youtrack/issueLoader'
|
import { youtrackHost } from '../../youtrack/issueLoader'
|
||||||
import { makeNonce } from '../KanbanPanel'
|
import { makeNonce } from '../KanbanPanel'
|
||||||
@@ -33,6 +33,7 @@ export async function handleSettingsSave(panel: KanbanWebviewPanel, payload: {
|
|||||||
implTabPreCreateScript: string
|
implTabPreCreateScript: string
|
||||||
implTabPostCloseScript: string
|
implTabPostCloseScript: string
|
||||||
conflictResolutionProfilePath: string
|
conflictResolutionProfilePath: string
|
||||||
|
commitProfilePath: string
|
||||||
codexModel: string
|
codexModel: string
|
||||||
codexReasoningEffort: string
|
codexReasoningEffort: string
|
||||||
profilesDirectory: string
|
profilesDirectory: string
|
||||||
@@ -64,6 +65,8 @@ export async function handleSettingsSave(panel: KanbanWebviewPanel, payload: {
|
|||||||
const trimmedImplPost = payload.implTabPostCloseScript.trim()
|
const trimmedImplPost = payload.implTabPostCloseScript.trim()
|
||||||
// conflict-resolution profile: '' is meaningful (= DEFAULT_PROFILE_PATH).
|
// conflict-resolution profile: '' is meaningful (= DEFAULT_PROFILE_PATH).
|
||||||
const trimmedConflictProfile = payload.conflictResolutionProfilePath.trim()
|
const trimmedConflictProfile = payload.conflictResolutionProfilePath.trim()
|
||||||
|
// 提交代码 profile:'' 有意义(= 未配置 → 提交严格失败),只 trim。
|
||||||
|
const trimmedCommitProfile = payload.commitProfilePath.trim()
|
||||||
// codex 模型 / 思考级别:空串有意义(= 用 codex 默认),只 trim。
|
// codex 模型 / 思考级别:空串有意义(= 用 codex 默认),只 trim。
|
||||||
const trimmedCodexModel = payload.codexModel.trim()
|
const trimmedCodexModel = payload.codexModel.trim()
|
||||||
const trimmedCodexReasoningEffort = payload.codexReasoningEffort.trim()
|
const trimmedCodexReasoningEffort = payload.codexReasoningEffort.trim()
|
||||||
@@ -99,6 +102,7 @@ export async function handleSettingsSave(panel: KanbanWebviewPanel, payload: {
|
|||||||
implTabPreCreateScript: trimmedImplPre,
|
implTabPreCreateScript: trimmedImplPre,
|
||||||
implTabPostCloseScript: trimmedImplPost,
|
implTabPostCloseScript: trimmedImplPost,
|
||||||
conflictResolutionProfilePath: trimmedConflictProfile,
|
conflictResolutionProfilePath: trimmedConflictProfile,
|
||||||
|
commitProfilePath: trimmedCommitProfile,
|
||||||
codexModel: trimmedCodexModel,
|
codexModel: trimmedCodexModel,
|
||||||
codexReasoningEffort: trimmedCodexReasoningEffort,
|
codexReasoningEffort: trimmedCodexReasoningEffort,
|
||||||
profilesDirectory: trimmedProfilesDirectory,
|
profilesDirectory: trimmedProfilesDirectory,
|
||||||
@@ -124,6 +128,7 @@ export async function handleSettingsSave(panel: KanbanWebviewPanel, payload: {
|
|||||||
implTabPreCreateScript: trimmedImplPre,
|
implTabPreCreateScript: trimmedImplPre,
|
||||||
implTabPostCloseScript: trimmedImplPost,
|
implTabPostCloseScript: trimmedImplPost,
|
||||||
conflictResolutionProfilePath: trimmedConflictProfile,
|
conflictResolutionProfilePath: trimmedConflictProfile,
|
||||||
|
commitProfilePath: trimmedCommitProfile,
|
||||||
codexModel: trimmedCodexModel,
|
codexModel: trimmedCodexModel,
|
||||||
codexReasoningEffort: trimmedCodexReasoningEffort,
|
codexReasoningEffort: trimmedCodexReasoningEffort,
|
||||||
profilesDirectory: trimmedProfilesDirectory,
|
profilesDirectory: trimmedProfilesDirectory,
|
||||||
@@ -194,6 +199,8 @@ export async function handleEditSettingsRequest(panel: KanbanWebviewPanel): Prom
|
|||||||
const ytTok = s.youtrackBaseUrl.trim()
|
const ytTok = s.youtrackBaseUrl.trim()
|
||||||
? await getYouTrackToken(panel.context, youtrackHost(s.youtrackBaseUrl.trim()))
|
? await getYouTrackToken(panel.context, youtrackHost(s.youtrackBaseUrl.trim()))
|
||||||
: undefined
|
: undefined
|
||||||
|
// 升级 seed:key 缺失时预填 deepseek path(若有),仅展示不写回。
|
||||||
|
const commitProfilePath = await getEffectiveCommitProfilePath(panel.context)
|
||||||
// User clicked the gear themselves — let them back out without saving.
|
// User clicked the gear themselves — let them back out without saving.
|
||||||
panel.postMessage({
|
panel.postMessage({
|
||||||
type: 'settings/show',
|
type: 'settings/show',
|
||||||
@@ -215,6 +222,7 @@ export async function handleEditSettingsRequest(panel: KanbanWebviewPanel): Prom
|
|||||||
implTabPreCreateScript: s.implTabPreCreateScript,
|
implTabPreCreateScript: s.implTabPreCreateScript,
|
||||||
implTabPostCloseScript: s.implTabPostCloseScript,
|
implTabPostCloseScript: s.implTabPostCloseScript,
|
||||||
conflictResolutionProfilePath: s.conflictResolutionProfilePath,
|
conflictResolutionProfilePath: s.conflictResolutionProfilePath,
|
||||||
|
commitProfilePath,
|
||||||
codexModel: s.codexModel,
|
codexModel: s.codexModel,
|
||||||
codexReasoningEffort: s.codexReasoningEffort,
|
codexReasoningEffort: s.codexReasoningEffort,
|
||||||
profilesDirectory: s.profilesDirectory,
|
profilesDirectory: s.profilesDirectory,
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
import type { Uri } from 'vscode'
|
import type { Uri } from 'vscode'
|
||||||
import type { KanbanWebviewPanel } from '../KanbanPanel'
|
import type { KanbanWebviewPanel } from '../KanbanPanel'
|
||||||
|
import { accessSync, constants } from 'node:fs'
|
||||||
|
import * as path from 'node:path'
|
||||||
import { extensions, workspace } from 'vscode'
|
import { extensions, workspace } from 'vscode'
|
||||||
import { listClaudeProfiles } from '../../cc/profiles'
|
import { resolveCommitProfileForRun } from '../../cc/commitProfile'
|
||||||
|
import { resolveProfilePath } from '../../cc/profiles'
|
||||||
import { spawnClaude } from '../../cc/spawnClaude'
|
import { spawnClaude } from '../../cc/spawnClaude'
|
||||||
import { findEnvFiles, lockEnvFiles, unlockEnvFiles } from '../../files/envLock'
|
import { findEnvFiles, lockEnvFiles, unlockEnvFiles } from '../../files/envLock'
|
||||||
import { checkBranchSync, runBranchSync } from '../../git/branchSync'
|
import { checkBranchSync, runBranchSync } from '../../git/branchSync'
|
||||||
import { logger } from '../../logging/logger'
|
import { logger } from '../../logging/logger'
|
||||||
import { getSettings } from '../../settings/store'
|
import { getEffectiveCommitProfilePath, getSettings } from '../../settings/store'
|
||||||
import { makeNonce } from '../KanbanPanel'
|
import { makeNonce } from '../KanbanPanel'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -54,14 +57,30 @@ export async function handleCommitRun(panel: KanbanWebviewPanel): Promise<void>
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const profiles = await listClaudeProfiles()
|
// ① 读有效 path(含升级 seed)→ ② 严格校验 → ③ 才标记 running
|
||||||
const deepseek = profiles.find(p => p.name === 'deepseek-v4-pro')
|
const raw = await getEffectiveCommitProfilePath(panel.context)
|
||||||
if (!deepseek) {
|
const resolution = resolveCommitProfileForRun(
|
||||||
|
raw,
|
||||||
|
resolveProfilePath,
|
||||||
|
(p) => {
|
||||||
|
try {
|
||||||
|
accessSync(p, constants.R_OK)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if (!resolution.ok) {
|
||||||
|
const message = resolution.reason === 'empty'
|
||||||
|
? '未配置提交代码 profile,请在设置中选择'
|
||||||
|
: `提交 profile 不可用:${path.basename(resolution.profilePath)}(路径:${resolution.profilePath})`
|
||||||
panel.postMessage({
|
panel.postMessage({
|
||||||
type: 'toast/show',
|
type: 'toast/show',
|
||||||
id: makeNonce(),
|
id: makeNonce(),
|
||||||
level: 'error',
|
level: 'error',
|
||||||
message: '未找到 deepseek profile(deepseek-v4-pro / deepseek),请在 Claude profiles 目录下创建对应 .json',
|
message,
|
||||||
dismissOnTimer: 8000,
|
dismissOnTimer: 8000,
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
@@ -76,7 +95,7 @@ export async function handleCommitRun(panel: KanbanWebviewPanel): Promise<void>
|
|||||||
await spawnClaude({
|
await spawnClaude({
|
||||||
prompt: '提交下代码',
|
prompt: '提交下代码',
|
||||||
cwd: workspaceRoot,
|
cwd: workspaceRoot,
|
||||||
profilePath: deepseek.path,
|
profilePath: resolution.profilePath,
|
||||||
timeoutMs: 30 * 60 * 1000,
|
timeoutMs: 30 * 60 * 1000,
|
||||||
bare: true,
|
bare: true,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ export type ExtensionToWebview
|
|||||||
implTabPreCreateScript: string
|
implTabPreCreateScript: string
|
||||||
implTabPostCloseScript: string
|
implTabPostCloseScript: string
|
||||||
conflictResolutionProfilePath: string
|
conflictResolutionProfilePath: string
|
||||||
|
commitProfilePath: string
|
||||||
codexModel: string
|
codexModel: string
|
||||||
codexReasoningEffort: string
|
codexReasoningEffort: string
|
||||||
profilesDirectory: string
|
profilesDirectory: string
|
||||||
@@ -133,6 +134,7 @@ export type WebviewToExtension
|
|||||||
implTabPreCreateScript: string
|
implTabPreCreateScript: string
|
||||||
implTabPostCloseScript: string
|
implTabPostCloseScript: string
|
||||||
conflictResolutionProfilePath: string
|
conflictResolutionProfilePath: string
|
||||||
|
commitProfilePath: string
|
||||||
codexModel: string
|
codexModel: string
|
||||||
codexReasoningEffort: string
|
codexReasoningEffort: string
|
||||||
profilesDirectory: string
|
profilesDirectory: string
|
||||||
|
|||||||
@@ -13,6 +13,8 @@
|
|||||||
import { readFileSync } from 'node:fs'
|
import { readFileSync } from 'node:fs'
|
||||||
import path from 'node:path'
|
import path from 'node:path'
|
||||||
import type { ExtensionContext } from 'vscode'
|
import type { ExtensionContext } from 'vscode'
|
||||||
|
import { listClaudeProfiles } from '../cc/profiles'
|
||||||
|
import { seedCommitProfilePath } from '../cc/commitProfile'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fallback prompt strings used when the on-disk markdown can't be read.
|
* Fallback prompt strings used when the on-disk markdown can't be read.
|
||||||
@@ -169,6 +171,11 @@ export interface Settings {
|
|||||||
* (drag to 完成 when PR merge conflicts). Empty = DEFAULT_PROFILE_PATH.
|
* (drag to 完成 when PR merge conflicts). Empty = DEFAULT_PROFILE_PATH.
|
||||||
*/
|
*/
|
||||||
conflictResolutionProfilePath: string
|
conflictResolutionProfilePath: string
|
||||||
|
/**
|
||||||
|
* 工具栏「提交代码」使用的 Claude profile 绝对路径。
|
||||||
|
* 空串 = 未配置 → 提交严格失败。升级时 key 缺失会 seed deepseek-v4-pro(若存在)。
|
||||||
|
*/
|
||||||
|
commitProfilePath: string
|
||||||
/**
|
/**
|
||||||
* codex 审查会话使用的模型(`-c model=`)。空串 = 不传,用 codex
|
* codex 审查会话使用的模型(`-c model=`)。空串 = 不传,用 codex
|
||||||
* `config.toml` 默认。
|
* `config.toml` 默认。
|
||||||
@@ -209,6 +216,7 @@ function defaults(ctx: ExtensionContext): Settings {
|
|||||||
youtrackProjectShortName: '',
|
youtrackProjectShortName: '',
|
||||||
youtrackCloseCommand: '',
|
youtrackCloseCommand: '',
|
||||||
conflictResolutionProfilePath: '',
|
conflictResolutionProfilePath: '',
|
||||||
|
commitProfilePath: '',
|
||||||
codexModel: '',
|
codexModel: '',
|
||||||
codexReasoningEffort: '',
|
codexReasoningEffort: '',
|
||||||
profilesDirectory: '',
|
profilesDirectory: '',
|
||||||
@@ -296,6 +304,11 @@ export function getSettings(ctx: ExtensionContext): Settings {
|
|||||||
const conflictResolutionProfilePath = typeof stored.conflictResolutionProfilePath === 'string'
|
const conflictResolutionProfilePath = typeof stored.conflictResolutionProfilePath === 'string'
|
||||||
? stored.conflictResolutionProfilePath
|
? stored.conflictResolutionProfilePath
|
||||||
: base.conflictResolutionProfilePath
|
: base.conflictResolutionProfilePath
|
||||||
|
// 提交 profile:'' 有意义(= 未配置 → 提交失败)。缺 key 时先给 '';
|
||||||
|
// deepseek seed 在 getEffectiveCommitProfilePath 里做(需异步 list profiles)。
|
||||||
|
const commitProfilePath = typeof stored.commitProfilePath === 'string'
|
||||||
|
? stored.commitProfilePath
|
||||||
|
: base.commitProfilePath
|
||||||
// codex 模型 / 思考级别:'' 有意义(= 用 codex 默认),不强制回退。
|
// codex 模型 / 思考级别:'' 有意义(= 用 codex 默认),不强制回退。
|
||||||
const codexModel = typeof stored.codexModel === 'string'
|
const codexModel = typeof stored.codexModel === 'string'
|
||||||
? stored.codexModel
|
? stored.codexModel
|
||||||
@@ -328,12 +341,26 @@ export function getSettings(ctx: ExtensionContext): Settings {
|
|||||||
youtrackProjectShortName,
|
youtrackProjectShortName,
|
||||||
youtrackCloseCommand,
|
youtrackCloseCommand,
|
||||||
conflictResolutionProfilePath,
|
conflictResolutionProfilePath,
|
||||||
|
commitProfilePath,
|
||||||
codexModel,
|
codexModel,
|
||||||
codexReasoningEffort,
|
codexReasoningEffort,
|
||||||
profilesDirectory,
|
profilesDirectory,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 读取工具栏「提交代码」有效的 profile path。
|
||||||
|
* ① raw stored 已有 key(含用户清空的 '')→ 原样返回
|
||||||
|
* ② key 从未出现 → list profiles 后 seed deepseek-v4-pro(不写回磁盘)
|
||||||
|
*/
|
||||||
|
export async function getEffectiveCommitProfilePath(ctx: ExtensionContext): Promise<string> {
|
||||||
|
const stored = ctx.globalState.get<Partial<Settings>>(SETTINGS_KEY) ?? {}
|
||||||
|
if (typeof stored.commitProfilePath === 'string')
|
||||||
|
return stored.commitProfilePath
|
||||||
|
const profiles = await listClaudeProfiles()
|
||||||
|
return seedCommitProfilePath(undefined, profiles)
|
||||||
|
}
|
||||||
|
|
||||||
export async function saveSettings(ctx: ExtensionContext, next: Partial<Settings>): Promise<void> {
|
export async function saveSettings(ctx: ExtensionContext, next: Partial<Settings>): Promise<void> {
|
||||||
const current = ctx.globalState.get<Partial<Settings>>(SETTINGS_KEY) ?? {}
|
const current = ctx.globalState.get<Partial<Settings>>(SETTINGS_KEY) ?? {}
|
||||||
const merged: Partial<Settings> = { ...current, ...next }
|
const merged: Partial<Settings> = { ...current, ...next }
|
||||||
|
|||||||
@@ -466,6 +466,7 @@ export function App() {
|
|||||||
initialImplTabPreCreateScript={settings?.implTabPreCreateScript ?? ''}
|
initialImplTabPreCreateScript={settings?.implTabPreCreateScript ?? ''}
|
||||||
initialImplTabPostCloseScript={settings?.implTabPostCloseScript ?? ''}
|
initialImplTabPostCloseScript={settings?.implTabPostCloseScript ?? ''}
|
||||||
initialConflictResolutionProfilePath={settings?.conflictResolutionProfilePath ?? ''}
|
initialConflictResolutionProfilePath={settings?.conflictResolutionProfilePath ?? ''}
|
||||||
|
initialCommitProfilePath={settings?.commitProfilePath ?? ''}
|
||||||
initialCodexModel={settings?.codexModel ?? ''}
|
initialCodexModel={settings?.codexModel ?? ''}
|
||||||
initialCodexReasoningEffort={settings?.codexReasoningEffort ?? ''}
|
initialCodexReasoningEffort={settings?.codexReasoningEffort ?? ''}
|
||||||
initialProfilesDirectory={settings?.profilesDirectory ?? ''}
|
initialProfilesDirectory={settings?.profilesDirectory ?? ''}
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ interface SubmitValues {
|
|||||||
implTabPreCreateScript: string
|
implTabPreCreateScript: string
|
||||||
implTabPostCloseScript: string
|
implTabPostCloseScript: string
|
||||||
conflictResolutionProfilePath: string
|
conflictResolutionProfilePath: string
|
||||||
|
commitProfilePath: string
|
||||||
codexModel: string
|
codexModel: string
|
||||||
codexReasoningEffort: string
|
codexReasoningEffort: string
|
||||||
profilesDirectory: string
|
profilesDirectory: string
|
||||||
@@ -75,6 +76,7 @@ export interface SettingsModalProps {
|
|||||||
initialImplTabPreCreateScript: string
|
initialImplTabPreCreateScript: string
|
||||||
initialImplTabPostCloseScript: string
|
initialImplTabPostCloseScript: string
|
||||||
initialConflictResolutionProfilePath: string
|
initialConflictResolutionProfilePath: string
|
||||||
|
initialCommitProfilePath: string
|
||||||
initialCodexModel: string
|
initialCodexModel: string
|
||||||
initialCodexReasoningEffort: string
|
initialCodexReasoningEffort: string
|
||||||
initialProfilesDirectory: string
|
initialProfilesDirectory: string
|
||||||
@@ -152,6 +154,7 @@ export function SettingsModal({
|
|||||||
initialImplTabPreCreateScript,
|
initialImplTabPreCreateScript,
|
||||||
initialImplTabPostCloseScript,
|
initialImplTabPostCloseScript,
|
||||||
initialConflictResolutionProfilePath,
|
initialConflictResolutionProfilePath,
|
||||||
|
initialCommitProfilePath,
|
||||||
initialCodexModel,
|
initialCodexModel,
|
||||||
initialCodexReasoningEffort,
|
initialCodexReasoningEffort,
|
||||||
initialProfilesDirectory,
|
initialProfilesDirectory,
|
||||||
@@ -183,6 +186,7 @@ export function SettingsModal({
|
|||||||
const [implTabPreCreateScript, setImplTabPreCreateScript] = useState(initialImplTabPreCreateScript)
|
const [implTabPreCreateScript, setImplTabPreCreateScript] = useState(initialImplTabPreCreateScript)
|
||||||
const [implTabPostCloseScript, setImplTabPostCloseScript] = useState(initialImplTabPostCloseScript)
|
const [implTabPostCloseScript, setImplTabPostCloseScript] = useState(initialImplTabPostCloseScript)
|
||||||
const [conflictResolutionProfilePath, setConflictResolutionProfilePath] = useState(initialConflictResolutionProfilePath)
|
const [conflictResolutionProfilePath, setConflictResolutionProfilePath] = useState(initialConflictResolutionProfilePath)
|
||||||
|
const [commitProfilePath, setCommitProfilePath] = useState(initialCommitProfilePath)
|
||||||
const [codexModel, setCodexModel] = useState(initialCodexModel)
|
const [codexModel, setCodexModel] = useState(initialCodexModel)
|
||||||
const [codexReasoningEffort, setCodexReasoningEffort] = useState(initialCodexReasoningEffort)
|
const [codexReasoningEffort, setCodexReasoningEffort] = useState(initialCodexReasoningEffort)
|
||||||
const [profilesDirectory, setProfilesDirectory] = useState(initialProfilesDirectory)
|
const [profilesDirectory, setProfilesDirectory] = useState(initialProfilesDirectory)
|
||||||
@@ -216,6 +220,7 @@ export function SettingsModal({
|
|||||||
setImplTabPreCreateScript(initialImplTabPreCreateScript)
|
setImplTabPreCreateScript(initialImplTabPreCreateScript)
|
||||||
setImplTabPostCloseScript(initialImplTabPostCloseScript)
|
setImplTabPostCloseScript(initialImplTabPostCloseScript)
|
||||||
setConflictResolutionProfilePath(initialConflictResolutionProfilePath)
|
setConflictResolutionProfilePath(initialConflictResolutionProfilePath)
|
||||||
|
setCommitProfilePath(initialCommitProfilePath)
|
||||||
setCodexModel(initialCodexModel)
|
setCodexModel(initialCodexModel)
|
||||||
setCodexReasoningEffort(initialCodexReasoningEffort)
|
setCodexReasoningEffort(initialCodexReasoningEffort)
|
||||||
setProfilesDirectory(initialProfilesDirectory)
|
setProfilesDirectory(initialProfilesDirectory)
|
||||||
@@ -302,6 +307,8 @@ export function SettingsModal({
|
|||||||
implTabPostCloseScript: implTabPostCloseScript.trim(),
|
implTabPostCloseScript: implTabPostCloseScript.trim(),
|
||||||
// 冲突解决 profile:空串 = 用 DEFAULT_PROFILE_PATH
|
// 冲突解决 profile:空串 = 用 DEFAULT_PROFILE_PATH
|
||||||
conflictResolutionProfilePath: conflictResolutionProfilePath.trim(),
|
conflictResolutionProfilePath: conflictResolutionProfilePath.trim(),
|
||||||
|
// 提交代码 profile:空串 = 未配置 → 提交严格失败
|
||||||
|
commitProfilePath: commitProfilePath.trim(),
|
||||||
// codex 模型 / 思考级别:trim 后留空 = 用 codex config.toml 默认
|
// codex 模型 / 思考级别:trim 后留空 = 用 codex config.toml 默认
|
||||||
codexModel: codexModel.trim(),
|
codexModel: codexModel.trim(),
|
||||||
codexReasoningEffort: codexReasoningEffort.trim(),
|
codexReasoningEffort: codexReasoningEffort.trim(),
|
||||||
@@ -484,6 +491,32 @@ export function SettingsModal({
|
|||||||
</select>
|
</select>
|
||||||
</Field>
|
</Field>
|
||||||
|
|
||||||
|
<Field
|
||||||
|
label="提交代码 profile"
|
||||||
|
hint={(
|
||||||
|
<>
|
||||||
|
顶部工具栏「提交代码」使用此 profile。留空则提交失败,不会回退默认。
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<select
|
||||||
|
value={commitProfilePath}
|
||||||
|
onChange={e => setCommitProfilePath(e.target.value)}
|
||||||
|
className={inputClass}
|
||||||
|
>
|
||||||
|
<option value="">(未配置)</option>
|
||||||
|
{profiles.map(p => (
|
||||||
|
<option key={p.path} value={p.path}>{p.name}</option>
|
||||||
|
))}
|
||||||
|
{commitProfilePath
|
||||||
|
&& !profiles.some(p => p.path === commitProfilePath) && (
|
||||||
|
<option value={commitProfilePath}>
|
||||||
|
{`自定义:${commitProfilePath}`}
|
||||||
|
</option>
|
||||||
|
)}
|
||||||
|
</select>
|
||||||
|
</Field>
|
||||||
|
|
||||||
<Field
|
<Field
|
||||||
label="codex 模型"
|
label="codex 模型"
|
||||||
hint={(
|
hint={(
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ export interface SettingsValues {
|
|||||||
implTabPreCreateScript: string
|
implTabPreCreateScript: string
|
||||||
implTabPostCloseScript: string
|
implTabPostCloseScript: string
|
||||||
conflictResolutionProfilePath: string
|
conflictResolutionProfilePath: string
|
||||||
|
commitProfilePath: string
|
||||||
codexModel: string
|
codexModel: string
|
||||||
codexReasoningEffort: string
|
codexReasoningEffort: string
|
||||||
profilesDirectory: string
|
profilesDirectory: string
|
||||||
@@ -70,6 +71,7 @@ export interface SettingsOverlayState {
|
|||||||
implTabPreCreateScript: string
|
implTabPreCreateScript: string
|
||||||
implTabPostCloseScript: string
|
implTabPostCloseScript: string
|
||||||
conflictResolutionProfilePath: string
|
conflictResolutionProfilePath: string
|
||||||
|
commitProfilePath: string
|
||||||
codexModel: string
|
codexModel: string
|
||||||
codexReasoningEffort: string
|
codexReasoningEffort: string
|
||||||
profilesDirectory: string
|
profilesDirectory: string
|
||||||
@@ -290,6 +292,7 @@ export function useIssues(): UseIssuesResult {
|
|||||||
implTabPreCreateScript: values.implTabPreCreateScript,
|
implTabPreCreateScript: values.implTabPreCreateScript,
|
||||||
implTabPostCloseScript: values.implTabPostCloseScript,
|
implTabPostCloseScript: values.implTabPostCloseScript,
|
||||||
conflictResolutionProfilePath: values.conflictResolutionProfilePath,
|
conflictResolutionProfilePath: values.conflictResolutionProfilePath,
|
||||||
|
commitProfilePath: values.commitProfilePath,
|
||||||
codexModel: values.codexModel,
|
codexModel: values.codexModel,
|
||||||
codexReasoningEffort: values.codexReasoningEffort,
|
codexReasoningEffort: values.codexReasoningEffort,
|
||||||
profilesDirectory: values.profilesDirectory,
|
profilesDirectory: values.profilesDirectory,
|
||||||
@@ -636,6 +639,7 @@ export function useIssues(): UseIssuesResult {
|
|||||||
implTabPreCreateScript: msg.implTabPreCreateScript,
|
implTabPreCreateScript: msg.implTabPreCreateScript,
|
||||||
implTabPostCloseScript: msg.implTabPostCloseScript,
|
implTabPostCloseScript: msg.implTabPostCloseScript,
|
||||||
conflictResolutionProfilePath: msg.conflictResolutionProfilePath,
|
conflictResolutionProfilePath: msg.conflictResolutionProfilePath,
|
||||||
|
commitProfilePath: msg.commitProfilePath,
|
||||||
codexModel: msg.codexModel,
|
codexModel: msg.codexModel,
|
||||||
codexReasoningEffort: msg.codexReasoningEffort,
|
codexReasoningEffort: msg.codexReasoningEffort,
|
||||||
profilesDirectory: msg.profilesDirectory,
|
profilesDirectory: msg.profilesDirectory,
|
||||||
|
|||||||
@@ -82,6 +82,7 @@ export type ExtensionToWebview
|
|||||||
implTabPreCreateScript: string
|
implTabPreCreateScript: string
|
||||||
implTabPostCloseScript: string
|
implTabPostCloseScript: string
|
||||||
conflictResolutionProfilePath: string
|
conflictResolutionProfilePath: string
|
||||||
|
commitProfilePath: string
|
||||||
codexModel: string
|
codexModel: string
|
||||||
codexReasoningEffort: string
|
codexReasoningEffort: string
|
||||||
profilesDirectory: string
|
profilesDirectory: string
|
||||||
@@ -148,6 +149,7 @@ export type WebviewToExtension
|
|||||||
implTabPreCreateScript: string
|
implTabPreCreateScript: string
|
||||||
implTabPostCloseScript: string
|
implTabPostCloseScript: string
|
||||||
conflictResolutionProfilePath: string
|
conflictResolutionProfilePath: string
|
||||||
|
commitProfilePath: string
|
||||||
codexModel: string
|
codexModel: string
|
||||||
codexReasoningEffort: string
|
codexReasoningEffort: string
|
||||||
profilesDirectory: string
|
profilesDirectory: string
|
||||||
|
|||||||
Reference in New Issue
Block a user