claude-code-best/src/utils/statusNoticeDefinitions.tsx
y574444354 39d21d27c9 merge: sync upstream claude-code-best/claude-code main (dad3ad2)
同步上游主仓库最新代码,整合以下新功能:
- feat: 工具层及 mcp 大重构 (#252)
- feat: 远程群控 (#243)
- feat: 添加对 langfuse 监控的支持 (#242)
- feat: 完成第一个 mcp-chrome 接入版本
- feat: 添加模型 1M 上下文切换
- feat: langfuse 工具调用显示为嵌套结构
- feat: 加上 userId 的传递
- feat: Add Ultraplan Feature for Advanced Multi-Agent Planning (#232)
- feat: Add brave as alternative WebSearchTool
- fix: 修复 Bun.hash 不存在问题
- fix: 修复缓存
- fix: 修复 interrupt 日志不上传
- fix: 修复 chrome 链接版本
- fix: 使用简化版本的 chrome 桥接器
- fix: 修复 node 环境没有 bun 的问题
- fix: 修复穷鬼模式的写入问题
- fix: 修复对穷鬼模式的 auto dream 和 session memory 越过
- fix: 修复初次登陆的校验问题
- fix(openai): fix stop_reason null, zero usage fields and max_tokens forwarding
- chore: 1.3.3 版本
- docs: 添加浏览器说明支持

冲突解决策略:
- 保留 CoStrict 品牌、4.0.x 版本号、csc bin 命令
- 保留 CoStrict 企业认证体系和 Agent 编排
- 采用上游的 1M context、langfuse、chrome mcp、远程群控等新功能
- 采用上游的 getDialogConfig/promptIdentifier 模式
- 采用上游的 FORCE_INTERACTIVE TTY 修复
- 合并两者:保留 CoStrict 品牌名 + 上游新代码模式
2026-04-14 11:18:05 +08:00

266 lines
8.3 KiB
TypeScript

// biome-ignore-all assist/source/organizeImports: ANT-ONLY import markers must not be reordered
import { Box, Text } from '@anthropic/ink'
import * as React from 'react'
import {
getLargeMemoryFiles,
MAX_MEMORY_CHARACTER_COUNT,
type MemoryFileInfo,
} from './claudemd.js'
import figures from 'figures'
import { getCwd } from './cwd.js'
import { relative } from 'path'
import { formatNumber } from './format.js'
import type { getGlobalConfig } from './config.js'
import {
getAnthropicApiKeyWithSource,
getApiKeyFromConfigOrMacOSKeychain,
getAuthTokenSource,
isClaudeAISubscriber,
} from './auth.js'
import type { AgentDefinitionsResult } from '@claude-code-best/builtin-tools/tools/AgentTool/loadAgentsDir.js'
import {
getAgentDescriptionsTotalTokens,
AGENT_DESCRIPTIONS_THRESHOLD,
} from './statusNoticeHelpers.js'
import {
isSupportedJetBrainsTerminal,
toIDEDisplayName,
getTerminalIdeType,
} from './ide.js'
import { isJetBrainsPluginInstalledCachedSync } from './jetbrains.js'
// Types
export type StatusNoticeType = 'warning' | 'info'
export type StatusNoticeContext = {
config: ReturnType<typeof getGlobalConfig>
agentDefinitions?: AgentDefinitionsResult
memoryFiles: MemoryFileInfo[]
}
export type StatusNoticeDefinition = {
id: string
type: StatusNoticeType
isActive: (context: StatusNoticeContext) => boolean
render: (context: StatusNoticeContext) => React.ReactNode
}
// Individual notice definitions
const largeMemoryFilesNotice: StatusNoticeDefinition = {
id: 'large-memory-files',
type: 'warning',
isActive: ctx => getLargeMemoryFiles(ctx.memoryFiles).length > 0,
render: ctx => {
const largeMemoryFiles = getLargeMemoryFiles(ctx.memoryFiles)
return (
<>
{largeMemoryFiles.map(file => {
const displayPath = file.path.startsWith(getCwd())
? relative(getCwd(), file.path)
: file.path
return (
<Box key={file.path} flexDirection="row">
<Text color="warning">{figures.warning}</Text>
<Text color="warning">
Large <Text bold>{displayPath}</Text> will impact performance (
{formatNumber(file.content.length)} chars &gt;{' '}
{formatNumber(MAX_MEMORY_CHARACTER_COUNT)})
<Text dimColor> · /memory to edit</Text>
</Text>
</Box>
)
})}
</>
)
},
}
const claudeAiSubscriberExternalTokenNotice: StatusNoticeDefinition = {
id: 'claude-ai-external-token',
type: 'warning',
isActive: () => {
const authTokenInfo = getAuthTokenSource()
return (
isClaudeAISubscriber() &&
(authTokenInfo.source === 'ANTHROPIC_AUTH_TOKEN' ||
authTokenInfo.source === 'apiKeyHelper')
)
},
render: () => {
const authTokenInfo = getAuthTokenSource()
return (
<Box flexDirection="row" marginTop={1}>
<Text color="warning">{figures.warning}</Text>
<Text color="warning">
Auth conflict: Using {authTokenInfo.source} instead of Claude account
subscription token. Either unset {authTokenInfo.source}, or run
`claude /logout`.
</Text>
</Box>
)
},
}
const apiKeyConflictNotice: StatusNoticeDefinition = {
id: 'api-key-conflict',
type: 'warning',
isActive: () => {
const { source: apiKeySource } = getAnthropicApiKeyWithSource({
skipRetrievingKeyFromApiKeyHelper: true,
})
return (
!!getApiKeyFromConfigOrMacOSKeychain() &&
(apiKeySource === 'ANTHROPIC_API_KEY' || apiKeySource === 'apiKeyHelper')
)
},
render: () => {
const { source: apiKeySource } = getAnthropicApiKeyWithSource({
skipRetrievingKeyFromApiKeyHelper: true,
})
return (
<Box flexDirection="row" marginTop={1}>
<Text color="warning">{figures.warning}</Text>
<Text color="warning">
Auth conflict: Using {apiKeySource} instead of Anthropic Console key.
Either unset {apiKeySource}, or run `claude /logout`.
</Text>
</Box>
)
},
}
const bothAuthMethodsNotice: StatusNoticeDefinition = {
id: 'both-auth-methods',
type: 'warning',
isActive: () => {
const { source: apiKeySource } = getAnthropicApiKeyWithSource({
skipRetrievingKeyFromApiKeyHelper: true,
})
const authTokenInfo = getAuthTokenSource()
return (
apiKeySource !== 'none' &&
authTokenInfo.source !== 'none' &&
!(
apiKeySource === 'apiKeyHelper' &&
authTokenInfo.source === 'apiKeyHelper'
)
)
},
render: () => {
const { source: apiKeySource } = getAnthropicApiKeyWithSource({
skipRetrievingKeyFromApiKeyHelper: true,
})
const authTokenInfo = getAuthTokenSource()
return (
<Box flexDirection="column" marginTop={1}>
<Box flexDirection="row">
<Text color="warning">{figures.warning}</Text>
<Text color="warning">
Auth conflict: Both a token ({authTokenInfo.source}) and an API key
({apiKeySource}) are set. This may lead to unexpected behavior.
</Text>
</Box>
<Box flexDirection="column" marginLeft={3}>
<Text color="warning">
· Trying to use{' '}
{authTokenInfo.source === 'claude.ai'
? 'costrict.ai'
: authTokenInfo.source}
?{' '}
{apiKeySource === 'ANTHROPIC_API_KEY'
? 'Unset the ANTHROPIC_API_KEY environment variable, or claude /logout then say "No" to the API key approval before login.'
: apiKeySource === 'apiKeyHelper'
? 'Unset the apiKeyHelper setting.'
: 'claude /logout'}
</Text>
<Text color="warning">
· Trying to use {apiKeySource}?{' '}
{authTokenInfo.source === 'claude.ai'
? 'claude /logout to sign out of costrict.ai.'
: `Unset the ${authTokenInfo.source} environment variable.`}
</Text>
</Box>
</Box>
)
},
}
const largeAgentDescriptionsNotice: StatusNoticeDefinition = {
id: 'large-agent-descriptions',
type: 'warning',
isActive: context => {
const totalTokens = getAgentDescriptionsTotalTokens(
context.agentDefinitions,
)
return totalTokens > AGENT_DESCRIPTIONS_THRESHOLD
},
render: context => {
const totalTokens = getAgentDescriptionsTotalTokens(
context.agentDefinitions,
)
return (
<Box flexDirection="row">
<Text color="warning">{figures.warning}</Text>
<Text color="warning">
Large cumulative agent descriptions will impact performance (~
{formatNumber(totalTokens)} tokens &gt;{' '}
{formatNumber(AGENT_DESCRIPTIONS_THRESHOLD)})
<Text dimColor> · /agents to manage</Text>
</Text>
</Box>
)
},
}
const jetbrainsPluginNotice: StatusNoticeDefinition = {
id: 'jetbrains-plugin-install',
type: 'info',
isActive: context => {
// Only show if running in JetBrains built-in terminal
if (!isSupportedJetBrainsTerminal()) {
return false
}
// Don't show if auto-install is disabled
const shouldAutoInstall = context.config.autoInstallIdeExtension ?? true
if (!shouldAutoInstall) {
return false
}
// Check if plugin is already installed (cached to avoid repeated filesystem checks)
const ideType = getTerminalIdeType()
return ideType !== null && !isJetBrainsPluginInstalledCachedSync(ideType)
},
render: () => {
const ideType = getTerminalIdeType()
const ideName = toIDEDisplayName(ideType)
return (
<Box flexDirection="row" gap={1} marginLeft={1}>
<Text color="ide">{figures.arrowUp}</Text>
<Text>
Install the <Text color="ide">{ideName}</Text> plugin from the
JetBrains Marketplace:{' '}
<Text bold>https://costrict.ai/docs/jetbrains</Text>
</Text>
</Box>
)
},
}
// All notice definitions
export const statusNoticeDefinitions: StatusNoticeDefinition[] = [
largeMemoryFilesNotice,
largeAgentDescriptionsNotice,
claudeAiSubscriberExternalTokenNotice,
apiKeyConflictNotice,
bothAuthMethodsNotice,
jetbrainsPluginNotice,
]
// Helper functions for external use
export function getActiveNotices(
context: StatusNoticeContext,
): StatusNoticeDefinition[] {
return statusNoticeDefinitions.filter(notice => notice.isActive(context))
}