fix plan mode branding and plan path (#1062)

This commit is contained in:
TechBrewBoss
2026-05-08 15:30:41 +08:00
committed by GitHub
parent 4830d6f778
commit 16726399d8
7 changed files with 67 additions and 13 deletions
@@ -13,7 +13,7 @@ export function RejectedPlanMessage(t0) {
} = t0;
let t1;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t1 = <Text color="subtle">User rejected Claude's plan:</Text>;
t1 = <Text color="subtle">User rejected the plan:</Text>;
$[0] = t1;
} else {
t1 = $[0];
@@ -23,7 +23,7 @@ import { toIDEDisplayName } from '../../../utils/ide.js';
import { logError } from '../../../utils/log.js';
import { enqueuePendingNotification } from '../../../utils/messageQueueManager.js';
import { createUserMessage } from '../../../utils/messages.js';
import { getMainLoopModel, getRuntimeMainLoopModel } from '../../../utils/model/model.js';
import { getMainLoopModel, getRuntimeMainLoopModel, modelDisplayString } from '../../../utils/model/model.js';
import { createPromptRuleContent, isClassifierPermissionsEnabled, PROMPT_PREFIX } from '../../../utils/permissions/bashClassifier.js';
import { type PermissionMode, toExternalPermissionMode } from '../../../utils/permissions/PermissionMode.js';
import type { PermissionUpdate } from '../../../utils/permissions/PermissionUpdateSchema.js';
@@ -148,14 +148,16 @@ export function ExitPlanModePermissionRequest({
isAutoModeAvailable,
isBypassPermissionsModeAvailable
} = toolPermissionContext;
const planAuthorName = modelDisplayString(toolUseConfirm.assistantMessage.message.model);
const options = useMemo(() => buildPlanApprovalOptions({
showClearContext,
showUltraplan,
usedPercent: showClearContext ? getContextUsedPercent(usage, mode) : null,
isAutoModeAvailable,
isBypassPermissionsModeAvailable,
planAuthorName,
onFeedbackChange: setPlanFeedback
}), [showClearContext, showUltraplan, usage, mode, isAutoModeAvailable, isBypassPermissionsModeAvailable]);
}), [showClearContext, showUltraplan, usage, mode, isAutoModeAvailable, isBypassPermissionsModeAvailable, planAuthorName]);
function onImagePaste(base64Image: string, mediaType?: string, filename?: string, dimensions?: ImageDimensions, _sourcePath?: string) {
const pasteId = nextPasteIdRef.current++;
const newContent: PastedContent = {
@@ -627,7 +629,7 @@ export function ExitPlanModePermissionRequest({
<PermissionDialog color="planMode" title="Ready to code?" innerPaddingX={0} workerBadge={workerBadge}>
<Box flexDirection="column" marginTop={1}>
<Box paddingX={1} flexDirection="column">
<Text>Here is Claude&apos;s plan:</Text>
<Text>Here is {planAuthorName}&apos;s plan:</Text>
</Box>
<Box borderColor="subtle" borderStyle="dashed" flexDirection="column" borderLeft={false} borderRight={false} paddingX={1} marginBottom={1}
// Necessary for Windows Terminal to render properly
@@ -644,7 +646,7 @@ export function ExitPlanModePermissionRequest({
</Box>}
{!useStickyFooter && <>
<Text dimColor>
Claude has written up a plan and is ready to execute. Would
{planAuthorName} has written up a plan and is ready to execute. Would
you like to proceed?
</Text>
<Box marginTop={1}>
@@ -677,6 +679,7 @@ export function buildPlanApprovalOptions({
usedPercent,
isAutoModeAvailable,
isBypassPermissionsModeAvailable,
planAuthorName,
onFeedbackChange
}: {
showClearContext: boolean;
@@ -684,6 +687,7 @@ export function buildPlanApprovalOptions({
usedPercent: number | null;
isAutoModeAvailable: boolean | undefined;
isBypassPermissionsModeAvailable: boolean | undefined;
planAuthorName: string;
onFeedbackChange: (v: string) => void;
}): OptionWithDescription<ResponseValue>[] {
const options: OptionWithDescription<ResponseValue>[] = [];
@@ -738,7 +742,7 @@ export function buildPlanApprovalOptions({
type: 'input',
label: 'No, keep planning',
value: 'no',
placeholder: 'Tell Claude what to change',
placeholder: `Tell ${planAuthorName} what to change`,
description: 'shift+tab to approve with this feedback',
onChange: onFeedbackChange
});
+1 -1
View File
@@ -55,7 +55,7 @@ export function renderToolResultMessage(output: Output, _progressMessagesForMess
return <Box flexDirection="column" marginTop={1}>
<Box flexDirection="row">
<Text color={getModeColor('plan')}>{BLACK_CIRCLE}</Text>
<Text> User approved Claude&apos;s plan</Text>
<Text> User approved the plan</Text>
</Box>
<MessageResponse>
<Box flexDirection="column">
+2 -2
View File
@@ -11,6 +11,7 @@ import * as lockfile from './lockfile.js'
import { logError } from './log.js'
import { cleanupOldVersions } from './nativeInstaller/index.js'
import { cleanupOldPastes } from './pasteStore.js'
import { getDefaultPlansDirectory } from './plans.js'
import { getProjectsDir } from './sessionStorage.js'
import { getSettingsWithAllErrors } from './settings/allErrors.js'
import {
@@ -298,8 +299,7 @@ async function cleanupSingleDirectory(
}
export function cleanupOldPlanFiles(): Promise<CleanupResult> {
const plansDir = join(getClaudeConfigHomeDir(), 'plans')
return cleanupSingleDirectory(plansDir, '.md')
return cleanupSingleDirectory(getDefaultPlansDirectory(), '.md')
}
export async function cleanupOldFileHistoryBackups(): Promise<CleanupResult> {
+37
View File
@@ -18,6 +18,10 @@ async function importFreshLocalInstaller() {
return import(`./localInstaller.ts?ts=${Date.now()}-${Math.random()}`)
}
async function importFreshPlans() {
return import(`./plans.ts?ts=${Date.now()}-${Math.random()}`)
}
afterEach(() => {
process.env = { ...originalEnv }
process.argv = [...originalArgv]
@@ -51,6 +55,39 @@ describe('OpenClaude paths', () => {
).toBe(join(homedir(), '.claude'))
})
test('default plans directory uses ~/.openclaude/plans', async () => {
delete process.env.CLAUDE_CONFIG_DIR
const { getDefaultPlansDirectory } = await importFreshPlans()
expect(getDefaultPlansDirectory({ homeDir: homedir() })).toBe(
join(homedir(), '.openclaude', 'plans'),
)
})
test('default plans directory respects explicit CLAUDE_CONFIG_DIR', async () => {
const { getDefaultPlansDirectory } = await importFreshPlans()
expect(
getDefaultPlansDirectory({ configDirEnv: '/tmp/custom-openclaude' }),
).toBe(join('/tmp/custom-openclaude', 'plans'))
})
test('default plans directory normalizes generated path to NFC', async () => {
const { getDefaultPlansDirectory } = await importFreshPlans()
expect(
getDefaultPlansDirectory({ homeDir: '/tmp/cafe\u0301' }),
).toBe(join('/tmp/caf\u00e9', '.openclaude', 'plans'))
})
test('default plans directory normalizes explicit CLAUDE_CONFIG_DIR to NFC', async () => {
const { getDefaultPlansDirectory } = await importFreshPlans()
expect(
getDefaultPlansDirectory({ configDirEnv: '/tmp/cafe\u0301-openclaude' }),
).toBe(join('/tmp/caf\u00e9-openclaude', 'plans'))
})
test('uses CLAUDE_CONFIG_DIR override when provided', async () => {
process.env.CLAUDE_CONFIG_DIR = '/tmp/custom-openclaude'
const { getClaudeConfigHomeDir, resolveClaudeConfigHomeDir } =
+16 -3
View File
@@ -1,6 +1,7 @@
import { randomUUID } from 'crypto'
import { copyFile, writeFile } from 'fs/promises'
import memoize from 'lodash-es/memoize.js'
import { homedir } from 'os'
import { join, resolve, sep } from 'path'
import type { AgentId, SessionId } from 'src/types/ids.js'
import type { LogOption } from 'src/types/logs.js'
@@ -14,7 +15,6 @@ import { getPlanSlugCache, getSessionId } from '../bootstrap/state.js'
import { EXIT_PLAN_MODE_V2_TOOL_NAME } from '../tools/ExitPlanModeTool/constants.js'
import { getCwd } from './cwd.js'
import { logForDebugging } from './debug.js'
import { getClaudeConfigHomeDir } from './envUtils.js'
import { isENOENT } from './errors.js'
import { getEnvironmentKind } from './filePersistence/outputsScanner.js'
import { getFsImplementation } from './fsOperations.js'
@@ -24,6 +24,19 @@ import { generateWordSlug } from './words.js'
const MAX_SLUG_RETRIES = 10
export function getDefaultPlansDirectory({
configDirEnv = process.env.CLAUDE_CONFIG_DIR,
homeDir = homedir(),
}: {
configDirEnv?: string
homeDir?: string
} = {}): string {
if (configDirEnv) {
return join(configDirEnv.normalize('NFC'), 'plans')
}
return join(homeDir, '.openclaude', 'plans').normalize('NFC')
}
/**
* Get or generate a word slug for the current session's plan.
* The slug is generated lazily on first access and cached for the session.
@@ -91,13 +104,13 @@ export const getPlansDirectory = memoize(function getPlansDirectory(): string {
logError(
new Error(`plansDirectory must be within project root: ${settingsDir}`),
)
plansPath = join(getClaudeConfigHomeDir(), 'plans')
plansPath = getDefaultPlansDirectory()
} else {
plansPath = resolved
}
} else {
// Default
plansPath = join(getClaudeConfigHomeDir(), 'plans')
plansPath = getDefaultPlansDirectory()
}
// Ensure directory exists (mkdirSync with recursive: true is a no-op if it exists)
+1 -1
View File
@@ -856,7 +856,7 @@ export const SettingsSchema = lazySchema(() =>
.optional()
.describe(
'Custom directory for plan files, relative to project root. ' +
'If not set, defaults to ~/.claude/plans/',
'If not set, defaults to ~/.openclaude/plans/',
),
...(process.env.USER_TYPE === 'ant'
? {