✨ feat(vscode): 设置面板可配置 Claude profiles 目录并修正探测顺序
This commit is contained in:
@@ -0,0 +1,118 @@
|
|||||||
|
import { accessSync, constants, mkdtempSync, mkdirSync, 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 {
|
||||||
|
getProfilesDir,
|
||||||
|
resolveProfilePath,
|
||||||
|
setProfilesDirOverride,
|
||||||
|
} from './profiles'
|
||||||
|
|
||||||
|
const originalEnv = process.env.SUPERPOWERS_PROFILES_DIR
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
setProfilesDirOverride(undefined)
|
||||||
|
if (originalEnv === undefined)
|
||||||
|
delete process.env.SUPERPOWERS_PROFILES_DIR
|
||||||
|
else
|
||||||
|
process.env.SUPERPOWERS_PROFILES_DIR = originalEnv
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('getProfilesDir', () => {
|
||||||
|
it('env SUPERPOWERS_PROFILES_DIR 优先', () => {
|
||||||
|
const dir = mkdtempSync(path.join(os.tmpdir(), 'profiles-env-'))
|
||||||
|
try {
|
||||||
|
process.env.SUPERPOWERS_PROFILES_DIR = dir
|
||||||
|
setProfilesDirOverride('~/ignored')
|
||||||
|
expect(getProfilesDir()).toBe(path.resolve(dir))
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
rmSync(dir, { recursive: true, force: true })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('settings override 支持 ~ 并优先于 auto', () => {
|
||||||
|
delete process.env.SUPERPOWERS_PROFILES_DIR
|
||||||
|
const dir = mkdtempSync(path.join(os.tmpdir(), 'profiles-override-'))
|
||||||
|
try {
|
||||||
|
const tildePath = dir.replace(os.homedir(), '~')
|
||||||
|
setProfilesDirOverride(tildePath)
|
||||||
|
expect(getProfilesDir()).toBe(path.resolve(dir))
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
rmSync(dir, { recursive: true, force: true })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('空 override 时优先 cruldra-profile 真实源(若可读)', () => {
|
||||||
|
delete process.env.SUPERPOWERS_PROFILES_DIR
|
||||||
|
setProfilesDirOverride(undefined)
|
||||||
|
const preferred = path.join(os.homedir(), 'Sources', 'cruldra-profile', 'claude-config', 'profiles')
|
||||||
|
try {
|
||||||
|
accessSync(preferred, constants.R_OK)
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
// 本机无该目录则跳过(CI / 其它机器)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
expect(getProfilesDir()).toBe(preferred)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('resolveProfilePath', () => {
|
||||||
|
it('空值回退默认 profile 路径', () => {
|
||||||
|
delete process.env.SUPERPOWERS_PROFILES_DIR
|
||||||
|
const dir = mkdtempSync(path.join(os.tmpdir(), 'profiles-default-'))
|
||||||
|
try {
|
||||||
|
writeFileSync(path.join(dir, 'offical.json'), '{}')
|
||||||
|
setProfilesDirOverride(dir)
|
||||||
|
expect(resolveProfilePath(undefined)).toBe(path.join(dir, 'offical.json'))
|
||||||
|
expect(resolveProfilePath('')).toBe(path.join(dir, 'offical.json'))
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
rmSync(dir, { recursive: true, force: true })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('可读绝对路径原样返回', () => {
|
||||||
|
delete process.env.SUPERPOWERS_PROFILES_DIR
|
||||||
|
const dir = mkdtempSync(path.join(os.tmpdir(), 'profiles-readable-'))
|
||||||
|
try {
|
||||||
|
const file = path.join(dir, 'grok-4.5.json')
|
||||||
|
writeFileSync(file, '{}')
|
||||||
|
setProfilesDirOverride(dir)
|
||||||
|
expect(resolveProfilePath(file)).toBe(file)
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
rmSync(dir, { recursive: true, force: true })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('跨机绝对路径按 basename remap 到本机 profiles 目录', () => {
|
||||||
|
delete process.env.SUPERPOWERS_PROFILES_DIR
|
||||||
|
const dir = mkdtempSync(path.join(os.tmpdir(), 'profiles-remap-'))
|
||||||
|
try {
|
||||||
|
writeFileSync(path.join(dir, 'grok-4.5.json'), '{}')
|
||||||
|
setProfilesDirOverride(dir)
|
||||||
|
const foreign = '/Users/cruldra/.claude/profile-settings/grok-4.5.json'
|
||||||
|
expect(resolveProfilePath(foreign)).toBe(path.join(dir, 'grok-4.5.json'))
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
rmSync(dir, { recursive: true, force: true })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('无法 remap 时返回原路径', () => {
|
||||||
|
delete process.env.SUPERPOWERS_PROFILES_DIR
|
||||||
|
const dir = mkdtempSync(path.join(os.tmpdir(), 'profiles-miss-'))
|
||||||
|
try {
|
||||||
|
mkdirSync(dir, { recursive: true })
|
||||||
|
setProfilesDirOverride(dir)
|
||||||
|
const foreign = '/Users/cruldra/.claude/profile-settings/missing-model.json'
|
||||||
|
expect(resolveProfilePath(foreign)).toBe(foreign)
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
rmSync(dir, { recursive: true, force: true })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
+129
-13
@@ -1,17 +1,25 @@
|
|||||||
/**
|
/**
|
||||||
* Lists Claude settings profiles from a hardcoded directory. Each `.json`
|
* Lists Claude settings profiles from a resolved directory.
|
||||||
* file there is treated as a profile whose `name` is its basename without
|
* Each `.json` file is a profile: `name` = basename without extension,
|
||||||
* the extension and whose `path` is the absolute file path, suitable for
|
* `path` = absolute path for `claude --settings <path>`.
|
||||||
* passing to `claude --settings <path>`.
|
|
||||||
*
|
*
|
||||||
* Failures (directory missing, unreadable, etc.) are swallowed — the
|
* Resolution order for the profiles directory:
|
||||||
* caller treats an empty list as "no profile selector".
|
* 1. `$SUPERPOWERS_PROFILES_DIR` (env override)
|
||||||
|
* 2. settings `profilesDirectory` via setProfilesDirOverride (supports ~)
|
||||||
|
* 3. auto candidates (first readable):
|
||||||
|
* ~/Sources/cruldra-profile/claude-config/profiles
|
||||||
|
* ~/.claude/profile-settings
|
||||||
|
* ~/.claude/profiles
|
||||||
|
* 4. fallback to the cruldra-profile path for error messages
|
||||||
|
*
|
||||||
|
* Missing/unreadable dirs yield an empty list — never throws.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { accessSync, constants } from 'node:fs'
|
||||||
import { readdir } from 'node:fs/promises'
|
import { readdir } from 'node:fs/promises'
|
||||||
|
import { homedir } from 'node:os'
|
||||||
import * as path from 'node:path'
|
import * as path from 'node:path'
|
||||||
|
import { expandTilde } from '../git/worktree'
|
||||||
const PROFILES_DIR = '/home/cruldra/Sources/cruldra-profile/claude-config/profiles'
|
|
||||||
|
|
||||||
export interface ClaudeProfile {
|
export interface ClaudeProfile {
|
||||||
/** basename without .json */
|
/** basename without .json */
|
||||||
@@ -20,20 +28,100 @@ export interface ClaudeProfile {
|
|||||||
path: string
|
path: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Settings-panel override; empty/undefined means auto-detect. */
|
||||||
|
let profilesDirOverride: string | undefined
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List Claude settings profiles from the hardcoded directory. Returns an
|
* Apply settings `profilesDirectory` (or clear with empty/undefined).
|
||||||
* empty array if the directory is missing or unreadable; does not throw.
|
* Call on activate, settings open, and after settings save.
|
||||||
* Sorted alphabetically by name.
|
*/
|
||||||
|
export function setProfilesDirOverride(dir: string | undefined): void {
|
||||||
|
const t = dir?.trim()
|
||||||
|
profilesDirOverride = t ? t : undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
function isReadableDir(dir: string): boolean {
|
||||||
|
try {
|
||||||
|
accessSync(dir, constants.R_OK)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isReadableFile(file: string): boolean {
|
||||||
|
try {
|
||||||
|
accessSync(file, constants.R_OK)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve the Claude profiles directory without hardcoding a user home path.
|
||||||
|
* Prefers the first existing candidate; falls back to the conventional path
|
||||||
|
* under `$HOME` so error messages stay meaningful when nothing exists yet.
|
||||||
|
*/
|
||||||
|
export function getProfilesDir(): string {
|
||||||
|
// ① env 显式覆盖优先
|
||||||
|
const envOverride = process.env.SUPERPOWERS_PROFILES_DIR?.trim()
|
||||||
|
if (envOverride)
|
||||||
|
return path.resolve(envOverride)
|
||||||
|
|
||||||
|
// ② 设置面板 override(支持 ~)
|
||||||
|
if (profilesDirOverride)
|
||||||
|
return path.resolve(expandTilde(profilesDirOverride))
|
||||||
|
|
||||||
|
// ③ auto:真实 profile 源优先,再兼容常见链接/目录
|
||||||
|
const home = homedir()
|
||||||
|
const candidates = [
|
||||||
|
path.join(home, 'Sources', 'cruldra-profile', 'claude-config', 'profiles'),
|
||||||
|
path.join(home, '.claude', 'profile-settings'),
|
||||||
|
path.join(home, '.claude', 'profiles'),
|
||||||
|
]
|
||||||
|
for (const dir of candidates) {
|
||||||
|
if (isReadableDir(dir))
|
||||||
|
return dir
|
||||||
|
}
|
||||||
|
return candidates[0]!
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remap a stored profile path (often an absolute path from another machine)
|
||||||
|
* onto the current profiles directory by basename when the original is missing.
|
||||||
|
*/
|
||||||
|
export function resolveProfilePath(stored: string | undefined): string {
|
||||||
|
const raw = stored?.trim()
|
||||||
|
if (!raw)
|
||||||
|
return getDefaultProfilePath()
|
||||||
|
|
||||||
|
if (isReadableFile(raw))
|
||||||
|
return raw
|
||||||
|
|
||||||
|
const remapped = path.join(getProfilesDir(), path.basename(raw))
|
||||||
|
if (isReadableFile(remapped))
|
||||||
|
return remapped
|
||||||
|
|
||||||
|
return raw
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List Claude settings profiles. Empty array if the directory is missing
|
||||||
|
* or unreadable. Sorted alphabetically by name.
|
||||||
*/
|
*/
|
||||||
export async function listClaudeProfiles(): Promise<ClaudeProfile[]> {
|
export async function listClaudeProfiles(): Promise<ClaudeProfile[]> {
|
||||||
|
const dir = getProfilesDir()
|
||||||
try {
|
try {
|
||||||
const entries = await readdir(PROFILES_DIR)
|
const entries = await readdir(dir)
|
||||||
const profiles: ClaudeProfile[] = []
|
const profiles: ClaudeProfile[] = []
|
||||||
for (const entry of entries) {
|
for (const entry of entries) {
|
||||||
if (!entry.endsWith('.json'))
|
if (!entry.endsWith('.json'))
|
||||||
continue
|
continue
|
||||||
const name = entry.slice(0, -'.json'.length)
|
const name = entry.slice(0, -'.json'.length)
|
||||||
profiles.push({ name, path: path.join(PROFILES_DIR, entry) })
|
profiles.push({ name, path: path.join(dir, entry) })
|
||||||
}
|
}
|
||||||
profiles.sort((a, b) => a.name.localeCompare(b.name))
|
profiles.sort((a, b) => a.name.localeCompare(b.name))
|
||||||
return profiles
|
return profiles
|
||||||
@@ -42,3 +130,31 @@ export async function listClaudeProfiles(): Promise<ClaudeProfile[]> {
|
|||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default profile for new sessions: prefer `offical.json` (legacy spelling),
|
||||||
|
* then `official.json`. Path is always under getProfilesDir().
|
||||||
|
*/
|
||||||
|
export function getDefaultProfilePath(): string {
|
||||||
|
const dir = getProfilesDir()
|
||||||
|
for (const name of ['offical.json', 'official.json']) {
|
||||||
|
const p = path.join(dir, name)
|
||||||
|
if (isReadableFile(p))
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
return path.join(dir, 'offical.json')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Profile for PR diff summaries / background content generation.
|
||||||
|
* Prefer `deepseek-v4-pro`, then `deepseek.json`.
|
||||||
|
*/
|
||||||
|
export function getPrDiffSummaryProfilePath(): string {
|
||||||
|
const dir = getProfilesDir()
|
||||||
|
for (const name of ['deepseek-v4-pro.json', 'deepseek.json']) {
|
||||||
|
const p = path.join(dir, name)
|
||||||
|
if (isReadableFile(p))
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
return path.join(dir, 'deepseek-v4-pro.json')
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,7 +5,9 @@ import type {
|
|||||||
TreeItem,
|
TreeItem,
|
||||||
} from 'vscode'
|
} from 'vscode'
|
||||||
import { EventEmitter, commands, window } from 'vscode'
|
import { EventEmitter, commands, window } from 'vscode'
|
||||||
|
import { setProfilesDirOverride } from './cc/profiles'
|
||||||
import { KanbanWebviewPanel } from './panel/KanbanPanel'
|
import { KanbanWebviewPanel } from './panel/KanbanPanel'
|
||||||
|
import { getSettings } from './settings/store'
|
||||||
import { webhookCoordinator } from './webhook/coordinator'
|
import { webhookCoordinator } from './webhook/coordinator'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -26,6 +28,8 @@ class EmptyTreeProvider implements TreeDataProvider<never> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function activate(context: ExtensionContext): void {
|
export function activate(context: ExtensionContext): void {
|
||||||
|
// Profiles 目录 override 在 panel 未打开时也要生效(list / launch 共用)。
|
||||||
|
setProfilesDirOverride(getSettings(context).profilesDirectory || undefined)
|
||||||
// Start the webhook server immediately so PR callbacks are received even
|
// Start the webhook server immediately so PR callbacks are received even
|
||||||
// when the Kanban panel is closed. Disposed automatically via subscriptions.
|
// when the Kanban panel is closed. Disposed automatically via subscriptions.
|
||||||
webhookCoordinator.init(context)
|
webhookCoordinator.init(context)
|
||||||
|
|||||||
@@ -36,11 +36,8 @@ import * as worktree from './handlers/worktree'
|
|||||||
import * as youtrackIssues from './handlers/youtrackIssues'
|
import * as youtrackIssues from './handlers/youtrackIssues'
|
||||||
import { PALETTE, resolveIssueColor, themeColorIdToIconUri } from './issueColor'
|
import { PALETTE, resolveIssueColor, themeColorIdToIconUri } from './issueColor'
|
||||||
|
|
||||||
export const DEFAULT_PROFILE_PATH = '/home/cruldra/Sources/cruldra-profile/claude-config/profiles/offical.json'
|
/** Resolved at runtime via getProfilesDir() — do not hardcode user paths. */
|
||||||
|
export { getDefaultProfilePath, getPrDiffSummaryProfilePath } from '../cc/profiles'
|
||||||
/** PR 变更摘要等后台内容生成任务用的 profile:DeepSeek(自带 token + base_url,
|
|
||||||
* headless 稳定,不依赖订阅 OAuth,避免 403 Request not allowed)。 */
|
|
||||||
export const PR_DIFF_SUMMARY_PROFILE_PATH = '/home/cruldra/Sources/cruldra-profile/claude-config/profiles/deepseek.json'
|
|
||||||
|
|
||||||
export class KanbanWebviewPanel {
|
export class KanbanWebviewPanel {
|
||||||
static readonly viewType = 'superpowers.kanbanPanel'
|
static readonly viewType = 'superpowers.kanbanPanel'
|
||||||
@@ -861,6 +858,7 @@ export class KanbanWebviewPanel {
|
|||||||
conflictResolutionProfilePath: s.conflictResolutionProfilePath,
|
conflictResolutionProfilePath: s.conflictResolutionProfilePath,
|
||||||
codexModel: s.codexModel,
|
codexModel: s.codexModel,
|
||||||
codexReasoningEffort: s.codexReasoningEffort,
|
codexReasoningEffort: s.codexReasoningEffort,
|
||||||
|
profilesDirectory: s.profilesDirectory,
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -926,6 +924,7 @@ export class KanbanWebviewPanel {
|
|||||||
conflictResolutionProfilePath: s.conflictResolutionProfilePath,
|
conflictResolutionProfilePath: s.conflictResolutionProfilePath,
|
||||||
codexModel: s.codexModel,
|
codexModel: s.codexModel,
|
||||||
codexReasoningEffort: s.codexReasoningEffort,
|
codexReasoningEffort: s.codexReasoningEffort,
|
||||||
|
profilesDirectory: s.profilesDirectory,
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { buildCcCommand } from '../../cc/ccCommand'
|
|||||||
import { pollForNewSession, projectsDirFor } from '../../cc/sessionWatcher'
|
import { pollForNewSession, projectsDirFor } from '../../cc/sessionWatcher'
|
||||||
import { logger } from '../../logging/logger'
|
import { logger } from '../../logging/logger'
|
||||||
import { readManagedSessions, writeManagedSessions } from '../../sessions/managedStore'
|
import { readManagedSessions, writeManagedSessions } from '../../sessions/managedStore'
|
||||||
import { DEFAULT_PROFILE_PATH } from '../KanbanPanel'
|
import { resolveProfilePath } from '../../cc/profiles'
|
||||||
|
|
||||||
/** 默认会话名:有 prompt 取前 20 字符,否则用短 id(前 8 位)。 */
|
/** 默认会话名:有 prompt 取前 20 字符,否则用短 id(前 8 位)。 */
|
||||||
function defaultSessionName(sessionId: string, prompt?: string): string {
|
function defaultSessionName(sessionId: string, prompt?: string): string {
|
||||||
@@ -66,8 +66,9 @@ export async function handleManagedSessionsCreate(panel: KanbanWebviewPanel, pro
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const effectiveProfilePath
|
const effectiveProfilePath = resolveProfilePath(
|
||||||
= profilePath && profilePath.trim() !== '' ? profilePath : DEFAULT_PROFILE_PATH
|
profilePath && profilePath.trim() !== '' ? profilePath : undefined,
|
||||||
|
)
|
||||||
// 单引号会破坏下面的 shell 单引号包裹,防御性拒绝(与现有 handler 一致)。
|
// 单引号会破坏下面的 shell 单引号包裹,防御性拒绝(与现有 handler 一致)。
|
||||||
if (effectiveProfilePath.includes('\'')) {
|
if (effectiveProfilePath.includes('\'')) {
|
||||||
void window.showErrorMessage(
|
void window.showErrorMessage(
|
||||||
@@ -219,8 +220,9 @@ export async function handleManagedSessionsResume(panel: KanbanWebviewPanel, ses
|
|||||||
const data = await readManagedSessions(workspaceRoot)
|
const data = await readManagedSessions(workspaceRoot)
|
||||||
const target = data.sessions.find(s => s.id === sessionId)
|
const target = data.sessions.find(s => s.id === sessionId)
|
||||||
|
|
||||||
const effectiveProfilePath
|
const effectiveProfilePath = resolveProfilePath(
|
||||||
= target?.profilePath && target.profilePath.trim() !== '' ? target.profilePath : DEFAULT_PROFILE_PATH
|
target?.profilePath && target.profilePath.trim() !== '' ? target.profilePath : undefined,
|
||||||
|
)
|
||||||
if (effectiveProfilePath.includes('\'')) {
|
if (effectiveProfilePath.includes('\'')) {
|
||||||
void window.showErrorMessage(
|
void window.showErrorMessage(
|
||||||
`resume 失败:profilePath 含单引号,拒绝执行 (${effectiveProfilePath})`,
|
`resume 失败:profilePath 含单引号,拒绝执行 (${effectiveProfilePath})`,
|
||||||
|
|||||||
@@ -18,7 +18,8 @@ import { createWorktree, resolveWorktreePath } from '../../git/worktree'
|
|||||||
import { logger } from '../../logging/logger'
|
import { logger } from '../../logging/logger'
|
||||||
import { getSettings } from '../../settings/store'
|
import { getSettings } from '../../settings/store'
|
||||||
import { webhookCoordinator } from '../../webhook/coordinator'
|
import { webhookCoordinator } from '../../webhook/coordinator'
|
||||||
import { DEFAULT_PROFILE_PATH, makeNonce } from '../KanbanPanel'
|
import { resolveProfilePath } from '../../cc/profiles'
|
||||||
|
import { makeNonce } from '../KanbanPanel'
|
||||||
|
|
||||||
export async function handleResumeSession(panel: KanbanWebviewPanel, sessionId: string, profilePath?: string, relCwd?: string, issueNumber?: number): Promise<void> {
|
export async function handleResumeSession(panel: KanbanWebviewPanel, sessionId: string, profilePath?: string, relCwd?: string, issueNumber?: number): Promise<void> {
|
||||||
// kind 跟着 sessionRole 提前判定(原来散落在方法中部,提到入口是为了构造
|
// kind 跟着 sessionRole 提前判定(原来散落在方法中部,提到入口是为了构造
|
||||||
@@ -1562,9 +1563,11 @@ export async function startConflictResolution(panel: KanbanWebviewPanel, opts: {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
terminal.show(false)
|
terminal.show(false)
|
||||||
// 冲突解决只用全局设置 conflictResolutionProfilePath;空串 = DEFAULT_PROFILE_PATH。
|
// 冲突解决只用全局设置 conflictResolutionProfilePath;空串 = 默认。
|
||||||
const effectiveProfilePath
|
// 工单/设置里可能持久化了跨机绝对路径,统一走 resolveProfilePath remapping。
|
||||||
= settings.conflictResolutionProfilePath.trim() || DEFAULT_PROFILE_PATH
|
const effectiveProfilePath = resolveProfilePath(
|
||||||
|
settings.conflictResolutionProfilePath.trim() || undefined,
|
||||||
|
)
|
||||||
if (effectiveProfilePath.includes('\'')) {
|
if (effectiveProfilePath.includes('\'')) {
|
||||||
logger.add({
|
logger.add({
|
||||||
level: 'error',
|
level: 'error',
|
||||||
@@ -1603,17 +1606,14 @@ export async function startConflictResolution(panel: KanbanWebviewPanel, opts: {
|
|||||||
* (handleImplement / handleResumeSession when sessionKind === 'implement')
|
* (handleImplement / handleResumeSession when sessionKind === 'implement')
|
||||||
* should launch with. Priority:
|
* should launch with. Priority:
|
||||||
* 1. 工单级 `profilePath`(详情面板「实施配置文件」)
|
* 1. 工单级 `profilePath`(详情面板「实施配置文件」)
|
||||||
* 2. DEFAULT_PROFILE_PATH
|
* 2. getDefaultProfilePath()
|
||||||
*
|
*
|
||||||
* 冲突解决用全局 `settings.conflictResolutionProfilePath`。
|
* 冲突解决用全局 `settings.conflictResolutionProfilePath`。
|
||||||
* 头脑风暴用 resolveBrainstormProfilePath;测试用 resolveTestProfilePath。
|
* 头脑风暴用 resolveBrainstormProfilePath;测试用 resolveTestProfilePath。
|
||||||
* 三者互不回退。
|
* 三者互不回退。
|
||||||
*/
|
*/
|
||||||
export function resolveImplementProfilePath(_panel: KanbanWebviewPanel, issueLevelProfilePath: string | undefined): string {
|
export function resolveImplementProfilePath(_panel: KanbanWebviewPanel, issueLevelProfilePath: string | undefined): string {
|
||||||
const issueLevel = issueLevelProfilePath?.trim()
|
return resolveProfilePath(issueLevelProfilePath?.trim() || undefined)
|
||||||
if (issueLevel)
|
|
||||||
return issueLevel
|
|
||||||
return DEFAULT_PROFILE_PATH
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1621,7 +1621,7 @@ export function resolveImplementProfilePath(_panel: KanbanWebviewPanel, issueLev
|
|||||||
* 不回退实施 profile。
|
* 不回退实施 profile。
|
||||||
*/
|
*/
|
||||||
export function resolveBrainstormProfilePath(_panel: KanbanWebviewPanel, brainstormProfilePath: string | undefined): string {
|
export function resolveBrainstormProfilePath(_panel: KanbanWebviewPanel, brainstormProfilePath: string | undefined): string {
|
||||||
return brainstormProfilePath?.trim() || DEFAULT_PROFILE_PATH
|
return resolveProfilePath(brainstormProfilePath?.trim() || undefined)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1629,7 +1629,7 @@ export function resolveBrainstormProfilePath(_panel: KanbanWebviewPanel, brainst
|
|||||||
* 不回退实施 profile。
|
* 不回退实施 profile。
|
||||||
*/
|
*/
|
||||||
export function resolveTestProfilePath(_panel: KanbanWebviewPanel, testProfilePath: string | undefined): string {
|
export function resolveTestProfilePath(_panel: KanbanWebviewPanel, testProfilePath: string | undefined): string {
|
||||||
return testProfilePath?.trim() || DEFAULT_PROFILE_PATH
|
return resolveProfilePath(testProfilePath?.trim() || undefined)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import * as os from 'node:os'
|
|||||||
import * as path from 'node:path'
|
import * as path from 'node:path'
|
||||||
import { commands, env, Uri, workspace } from 'vscode'
|
import { commands, env, Uri, workspace } from 'vscode'
|
||||||
import { getToken, getYouTrackToken, setToken, setYouTrackToken } from '../../auth/secrets'
|
import { getToken, getYouTrackToken, setToken, setYouTrackToken } from '../../auth/secrets'
|
||||||
import { listClaudeProfiles } from '../../cc/profiles'
|
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'
|
||||||
@@ -35,6 +35,7 @@ export async function handleSettingsSave(panel: KanbanWebviewPanel, payload: {
|
|||||||
conflictResolutionProfilePath: string
|
conflictResolutionProfilePath: string
|
||||||
codexModel: string
|
codexModel: string
|
||||||
codexReasoningEffort: string
|
codexReasoningEffort: string
|
||||||
|
profilesDirectory: string
|
||||||
youtrackBaseUrl: string
|
youtrackBaseUrl: string
|
||||||
youtrackProjectShortName: string
|
youtrackProjectShortName: string
|
||||||
youtrackCloseCommand: string
|
youtrackCloseCommand: string
|
||||||
@@ -66,6 +67,8 @@ export async function handleSettingsSave(panel: KanbanWebviewPanel, payload: {
|
|||||||
// 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()
|
||||||
|
// profiles 目录:空串有意义(= 自动探测),只 trim。
|
||||||
|
const trimmedProfilesDirectory = payload.profilesDirectory.trim()
|
||||||
const prev = getSettings(panel.context)
|
const prev = getSettings(panel.context)
|
||||||
// Capture the previous token *for this host* before overwriting it, so
|
// Capture the previous token *for this host* before overwriting it, so
|
||||||
// we can decide below whether the kanban needs a re-fetch. (Only host
|
// we can decide below whether the kanban needs a re-fetch. (Only host
|
||||||
@@ -98,6 +101,7 @@ export async function handleSettingsSave(panel: KanbanWebviewPanel, payload: {
|
|||||||
conflictResolutionProfilePath: trimmedConflictProfile,
|
conflictResolutionProfilePath: trimmedConflictProfile,
|
||||||
codexModel: trimmedCodexModel,
|
codexModel: trimmedCodexModel,
|
||||||
codexReasoningEffort: trimmedCodexReasoningEffort,
|
codexReasoningEffort: trimmedCodexReasoningEffort,
|
||||||
|
profilesDirectory: trimmedProfilesDirectory,
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -122,10 +126,14 @@ export async function handleSettingsSave(panel: KanbanWebviewPanel, payload: {
|
|||||||
conflictResolutionProfilePath: trimmedConflictProfile,
|
conflictResolutionProfilePath: trimmedConflictProfile,
|
||||||
codexModel: trimmedCodexModel,
|
codexModel: trimmedCodexModel,
|
||||||
codexReasoningEffort: trimmedCodexReasoningEffort,
|
codexReasoningEffort: trimmedCodexReasoningEffort,
|
||||||
|
profilesDirectory: trimmedProfilesDirectory,
|
||||||
youtrackBaseUrl: trimmedYtBase,
|
youtrackBaseUrl: trimmedYtBase,
|
||||||
youtrackProjectShortName: trimmedYtProject,
|
youtrackProjectShortName: trimmedYtProject,
|
||||||
youtrackCloseCommand: trimmedYtClose,
|
youtrackCloseCommand: trimmedYtClose,
|
||||||
})
|
})
|
||||||
|
// 保存后立刻让 listClaudeProfiles / getDefaultProfilePath 吃到新目录。
|
||||||
|
setProfilesDirOverride(trimmedProfilesDirectory || undefined)
|
||||||
|
void handleProfilesList(panel)
|
||||||
if (!keepExisting)
|
if (!keepExisting)
|
||||||
await setToken(panel.context, trimmedHost, trimmedToken)
|
await setToken(panel.context, trimmedHost, trimmedToken)
|
||||||
// YouTrack token: stored under the base URL's host. Empty input keeps the
|
// YouTrack token: stored under the base URL's host. Empty input keeps the
|
||||||
@@ -179,6 +187,8 @@ export async function handleEditSettingsRequest(panel: KanbanWebviewPanel): Prom
|
|||||||
host = remote.host
|
host = remote.host
|
||||||
}
|
}
|
||||||
const s = getSettings(panel.context)
|
const s = getSettings(panel.context)
|
||||||
|
// 打开设置时同步 override,避免未 activate 路径或旧值漂移。
|
||||||
|
setProfilesDirOverride(s.profilesDirectory || undefined)
|
||||||
const tok = host ? await getToken(panel.context, host) : undefined
|
const tok = host ? await getToken(panel.context, host) : undefined
|
||||||
const tokenSaved = !!tok && tok.length > 0
|
const tokenSaved = !!tok && tok.length > 0
|
||||||
const ytTok = s.youtrackBaseUrl.trim()
|
const ytTok = s.youtrackBaseUrl.trim()
|
||||||
@@ -207,6 +217,7 @@ export async function handleEditSettingsRequest(panel: KanbanWebviewPanel): Prom
|
|||||||
conflictResolutionProfilePath: s.conflictResolutionProfilePath,
|
conflictResolutionProfilePath: s.conflictResolutionProfilePath,
|
||||||
codexModel: s.codexModel,
|
codexModel: s.codexModel,
|
||||||
codexReasoningEffort: s.codexReasoningEffort,
|
codexReasoningEffort: s.codexReasoningEffort,
|
||||||
|
profilesDirectory: s.profilesDirectory,
|
||||||
youtrackBaseUrl: s.youtrackBaseUrl,
|
youtrackBaseUrl: s.youtrackBaseUrl,
|
||||||
youtrackProjectShortName: s.youtrackProjectShortName,
|
youtrackProjectShortName: s.youtrackProjectShortName,
|
||||||
youtrackCloseCommand: s.youtrackCloseCommand,
|
youtrackCloseCommand: s.youtrackCloseCommand,
|
||||||
|
|||||||
@@ -69,6 +69,7 @@ export type ExtensionToWebview
|
|||||||
conflictResolutionProfilePath: string
|
conflictResolutionProfilePath: string
|
||||||
codexModel: string
|
codexModel: string
|
||||||
codexReasoningEffort: string
|
codexReasoningEffort: string
|
||||||
|
profilesDirectory: string
|
||||||
youtrackBaseUrl?: string
|
youtrackBaseUrl?: string
|
||||||
youtrackProjectShortName?: string
|
youtrackProjectShortName?: string
|
||||||
youtrackCloseCommand?: string
|
youtrackCloseCommand?: string
|
||||||
@@ -133,6 +134,7 @@ export type WebviewToExtension
|
|||||||
conflictResolutionProfilePath: string
|
conflictResolutionProfilePath: string
|
||||||
codexModel: string
|
codexModel: string
|
||||||
codexReasoningEffort: string
|
codexReasoningEffort: string
|
||||||
|
profilesDirectory: string
|
||||||
youtrackBaseUrl: string
|
youtrackBaseUrl: string
|
||||||
youtrackProjectShortName: string
|
youtrackProjectShortName: string
|
||||||
youtrackCloseCommand: string
|
youtrackCloseCommand: string
|
||||||
|
|||||||
@@ -179,6 +179,11 @@ export interface Settings {
|
|||||||
* 用 codex 默认。合法值 minimal/low/medium/high/xhigh。
|
* 用 codex 默认。合法值 minimal/low/medium/high/xhigh。
|
||||||
*/
|
*/
|
||||||
codexReasoningEffort: string
|
codexReasoningEffort: string
|
||||||
|
/**
|
||||||
|
* Claude `--settings` profile 所在目录。空串 = 自动探测。
|
||||||
|
* 支持前导 `~`。保存后 listClaudeProfiles / getDefaultProfilePath 都从这里读。
|
||||||
|
*/
|
||||||
|
profilesDirectory: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SETTINGS_KEY = 'superpowers.settings'
|
export const SETTINGS_KEY = 'superpowers.settings'
|
||||||
@@ -206,6 +211,7 @@ function defaults(ctx: ExtensionContext): Settings {
|
|||||||
conflictResolutionProfilePath: '',
|
conflictResolutionProfilePath: '',
|
||||||
codexModel: '',
|
codexModel: '',
|
||||||
codexReasoningEffort: '',
|
codexReasoningEffort: '',
|
||||||
|
profilesDirectory: '',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -297,6 +303,10 @@ export function getSettings(ctx: ExtensionContext): Settings {
|
|||||||
const codexReasoningEffort = typeof stored.codexReasoningEffort === 'string'
|
const codexReasoningEffort = typeof stored.codexReasoningEffort === 'string'
|
||||||
? stored.codexReasoningEffort
|
? stored.codexReasoningEffort
|
||||||
: base.codexReasoningEffort
|
: base.codexReasoningEffort
|
||||||
|
// profiles 目录:'' 有意义(= 自动探测),不强制回退。
|
||||||
|
const profilesDirectory = typeof stored.profilesDirectory === 'string'
|
||||||
|
? stored.profilesDirectory
|
||||||
|
: base.profilesDirectory
|
||||||
|
|
||||||
return {
|
return {
|
||||||
webhookPort,
|
webhookPort,
|
||||||
@@ -320,6 +330,7 @@ export function getSettings(ctx: ExtensionContext): Settings {
|
|||||||
conflictResolutionProfilePath,
|
conflictResolutionProfilePath,
|
||||||
codexModel,
|
codexModel,
|
||||||
codexReasoningEffort,
|
codexReasoningEffort,
|
||||||
|
profilesDirectory,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -460,6 +460,7 @@ export function App() {
|
|||||||
initialConflictResolutionProfilePath={settings?.conflictResolutionProfilePath ?? ''}
|
initialConflictResolutionProfilePath={settings?.conflictResolutionProfilePath ?? ''}
|
||||||
initialCodexModel={settings?.codexModel ?? ''}
|
initialCodexModel={settings?.codexModel ?? ''}
|
||||||
initialCodexReasoningEffort={settings?.codexReasoningEffort ?? ''}
|
initialCodexReasoningEffort={settings?.codexReasoningEffort ?? ''}
|
||||||
|
initialProfilesDirectory={settings?.profilesDirectory ?? ''}
|
||||||
initialYoutrackBaseUrl={settings?.youtrackBaseUrl ?? ''}
|
initialYoutrackBaseUrl={settings?.youtrackBaseUrl ?? ''}
|
||||||
initialYoutrackProjectShortName={settings?.youtrackProjectShortName ?? ''}
|
initialYoutrackProjectShortName={settings?.youtrackProjectShortName ?? ''}
|
||||||
initialYoutrackCloseCommand={settings?.youtrackCloseCommand ?? ''}
|
initialYoutrackCloseCommand={settings?.youtrackCloseCommand ?? ''}
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ interface SubmitValues {
|
|||||||
conflictResolutionProfilePath: string
|
conflictResolutionProfilePath: string
|
||||||
codexModel: string
|
codexModel: string
|
||||||
codexReasoningEffort: string
|
codexReasoningEffort: string
|
||||||
|
profilesDirectory: string
|
||||||
youtrackBaseUrl: string
|
youtrackBaseUrl: string
|
||||||
youtrackProjectShortName: string
|
youtrackProjectShortName: string
|
||||||
youtrackCloseCommand: string
|
youtrackCloseCommand: string
|
||||||
@@ -76,6 +77,7 @@ export interface SettingsModalProps {
|
|||||||
initialConflictResolutionProfilePath: string
|
initialConflictResolutionProfilePath: string
|
||||||
initialCodexModel: string
|
initialCodexModel: string
|
||||||
initialCodexReasoningEffort: string
|
initialCodexReasoningEffort: string
|
||||||
|
initialProfilesDirectory: string
|
||||||
initialYoutrackBaseUrl: string
|
initialYoutrackBaseUrl: string
|
||||||
initialYoutrackProjectShortName: string
|
initialYoutrackProjectShortName: string
|
||||||
initialYoutrackCloseCommand: string
|
initialYoutrackCloseCommand: string
|
||||||
@@ -152,6 +154,7 @@ export function SettingsModal({
|
|||||||
initialConflictResolutionProfilePath,
|
initialConflictResolutionProfilePath,
|
||||||
initialCodexModel,
|
initialCodexModel,
|
||||||
initialCodexReasoningEffort,
|
initialCodexReasoningEffort,
|
||||||
|
initialProfilesDirectory,
|
||||||
initialYoutrackBaseUrl,
|
initialYoutrackBaseUrl,
|
||||||
initialYoutrackProjectShortName,
|
initialYoutrackProjectShortName,
|
||||||
initialYoutrackCloseCommand,
|
initialYoutrackCloseCommand,
|
||||||
@@ -182,6 +185,7 @@ export function SettingsModal({
|
|||||||
const [conflictResolutionProfilePath, setConflictResolutionProfilePath] = useState(initialConflictResolutionProfilePath)
|
const [conflictResolutionProfilePath, setConflictResolutionProfilePath] = useState(initialConflictResolutionProfilePath)
|
||||||
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 [youtrackBaseUrl, setYoutrackBaseUrl] = useState(initialYoutrackBaseUrl)
|
const [youtrackBaseUrl, setYoutrackBaseUrl] = useState(initialYoutrackBaseUrl)
|
||||||
const [youtrackProjectShortName, setYoutrackProjectShortName] = useState(initialYoutrackProjectShortName)
|
const [youtrackProjectShortName, setYoutrackProjectShortName] = useState(initialYoutrackProjectShortName)
|
||||||
const [youtrackCloseCommand, setYoutrackCloseCommand] = useState(initialYoutrackCloseCommand)
|
const [youtrackCloseCommand, setYoutrackCloseCommand] = useState(initialYoutrackCloseCommand)
|
||||||
@@ -214,6 +218,7 @@ export function SettingsModal({
|
|||||||
setConflictResolutionProfilePath(initialConflictResolutionProfilePath)
|
setConflictResolutionProfilePath(initialConflictResolutionProfilePath)
|
||||||
setCodexModel(initialCodexModel)
|
setCodexModel(initialCodexModel)
|
||||||
setCodexReasoningEffort(initialCodexReasoningEffort)
|
setCodexReasoningEffort(initialCodexReasoningEffort)
|
||||||
|
setProfilesDirectory(initialProfilesDirectory)
|
||||||
setYoutrackBaseUrl(initialYoutrackBaseUrl)
|
setYoutrackBaseUrl(initialYoutrackBaseUrl)
|
||||||
setYoutrackProjectShortName(initialYoutrackProjectShortName)
|
setYoutrackProjectShortName(initialYoutrackProjectShortName)
|
||||||
setYoutrackCloseCommand(initialYoutrackCloseCommand)
|
setYoutrackCloseCommand(initialYoutrackCloseCommand)
|
||||||
@@ -300,6 +305,8 @@ export function SettingsModal({
|
|||||||
// codex 模型 / 思考级别:trim 后留空 = 用 codex config.toml 默认
|
// codex 模型 / 思考级别:trim 后留空 = 用 codex config.toml 默认
|
||||||
codexModel: codexModel.trim(),
|
codexModel: codexModel.trim(),
|
||||||
codexReasoningEffort: codexReasoningEffort.trim(),
|
codexReasoningEffort: codexReasoningEffort.trim(),
|
||||||
|
// profiles 目录:trim 后留空 = 自动探测
|
||||||
|
profilesDirectory: profilesDirectory.trim(),
|
||||||
youtrackBaseUrl: youtrackBaseUrl.trim(),
|
youtrackBaseUrl: youtrackBaseUrl.trim(),
|
||||||
youtrackProjectShortName: youtrackProjectShortName.trim(),
|
youtrackProjectShortName: youtrackProjectShortName.trim(),
|
||||||
youtrackCloseCommand: youtrackCloseCommand.trim(),
|
youtrackCloseCommand: youtrackCloseCommand.trim(),
|
||||||
@@ -559,6 +566,29 @@ export function SettingsModal({
|
|||||||
<span className="opacity-80">启用</span>
|
<span className="opacity-80">启用</span>
|
||||||
</label>
|
</label>
|
||||||
</Field>
|
</Field>
|
||||||
|
|
||||||
|
<Field
|
||||||
|
label="Claude profiles 目录"
|
||||||
|
hint={(
|
||||||
|
<>
|
||||||
|
claude --settings 用的 .json 所在目录。留空自动探测(优先
|
||||||
|
{' '}
|
||||||
|
<code>~/Sources/cruldra-profile/claude-config/profiles</code>
|
||||||
|
)。支持
|
||||||
|
{' '}
|
||||||
|
<code>~</code>
|
||||||
|
。
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={profilesDirectory}
|
||||||
|
onChange={e => setProfilesDirectory(e.target.value)}
|
||||||
|
placeholder="~/Sources/cruldra-profile/claude-config/profiles"
|
||||||
|
className={inputClass}
|
||||||
|
/>
|
||||||
|
</Field>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ export interface SettingsValues {
|
|||||||
conflictResolutionProfilePath: string
|
conflictResolutionProfilePath: string
|
||||||
codexModel: string
|
codexModel: string
|
||||||
codexReasoningEffort: string
|
codexReasoningEffort: string
|
||||||
|
profilesDirectory: string
|
||||||
youtrackBaseUrl: string
|
youtrackBaseUrl: string
|
||||||
youtrackProjectShortName: string
|
youtrackProjectShortName: string
|
||||||
youtrackCloseCommand: string
|
youtrackCloseCommand: string
|
||||||
@@ -71,6 +72,7 @@ export interface SettingsOverlayState {
|
|||||||
conflictResolutionProfilePath: string
|
conflictResolutionProfilePath: string
|
||||||
codexModel: string
|
codexModel: string
|
||||||
codexReasoningEffort: string
|
codexReasoningEffort: string
|
||||||
|
profilesDirectory: string
|
||||||
youtrackBaseUrl: string
|
youtrackBaseUrl: string
|
||||||
youtrackProjectShortName: string
|
youtrackProjectShortName: string
|
||||||
youtrackCloseCommand: string
|
youtrackCloseCommand: string
|
||||||
@@ -290,6 +292,7 @@ export function useIssues(): UseIssuesResult {
|
|||||||
conflictResolutionProfilePath: values.conflictResolutionProfilePath,
|
conflictResolutionProfilePath: values.conflictResolutionProfilePath,
|
||||||
codexModel: values.codexModel,
|
codexModel: values.codexModel,
|
||||||
codexReasoningEffort: values.codexReasoningEffort,
|
codexReasoningEffort: values.codexReasoningEffort,
|
||||||
|
profilesDirectory: values.profilesDirectory,
|
||||||
youtrackBaseUrl: values.youtrackBaseUrl,
|
youtrackBaseUrl: values.youtrackBaseUrl,
|
||||||
youtrackProjectShortName: values.youtrackProjectShortName,
|
youtrackProjectShortName: values.youtrackProjectShortName,
|
||||||
youtrackCloseCommand: values.youtrackCloseCommand,
|
youtrackCloseCommand: values.youtrackCloseCommand,
|
||||||
@@ -635,6 +638,7 @@ export function useIssues(): UseIssuesResult {
|
|||||||
conflictResolutionProfilePath: msg.conflictResolutionProfilePath,
|
conflictResolutionProfilePath: msg.conflictResolutionProfilePath,
|
||||||
codexModel: msg.codexModel,
|
codexModel: msg.codexModel,
|
||||||
codexReasoningEffort: msg.codexReasoningEffort,
|
codexReasoningEffort: msg.codexReasoningEffort,
|
||||||
|
profilesDirectory: msg.profilesDirectory,
|
||||||
youtrackBaseUrl: msg.youtrackBaseUrl ?? '',
|
youtrackBaseUrl: msg.youtrackBaseUrl ?? '',
|
||||||
youtrackProjectShortName: msg.youtrackProjectShortName ?? '',
|
youtrackProjectShortName: msg.youtrackProjectShortName ?? '',
|
||||||
youtrackCloseCommand: msg.youtrackCloseCommand ?? '',
|
youtrackCloseCommand: msg.youtrackCloseCommand ?? '',
|
||||||
|
|||||||
@@ -84,6 +84,7 @@ export type ExtensionToWebview
|
|||||||
conflictResolutionProfilePath: string
|
conflictResolutionProfilePath: string
|
||||||
codexModel: string
|
codexModel: string
|
||||||
codexReasoningEffort: string
|
codexReasoningEffort: string
|
||||||
|
profilesDirectory: string
|
||||||
youtrackBaseUrl?: string
|
youtrackBaseUrl?: string
|
||||||
youtrackProjectShortName?: string
|
youtrackProjectShortName?: string
|
||||||
youtrackCloseCommand?: string
|
youtrackCloseCommand?: string
|
||||||
@@ -148,6 +149,7 @@ export type WebviewToExtension
|
|||||||
conflictResolutionProfilePath: string
|
conflictResolutionProfilePath: string
|
||||||
codexModel: string
|
codexModel: string
|
||||||
codexReasoningEffort: string
|
codexReasoningEffort: string
|
||||||
|
profilesDirectory: string
|
||||||
youtrackBaseUrl: string
|
youtrackBaseUrl: string
|
||||||
youtrackProjectShortName: string
|
youtrackProjectShortName: string
|
||||||
youtrackCloseCommand: string
|
youtrackCloseCommand: string
|
||||||
|
|||||||
Reference in New Issue
Block a user