Phase 002: rebrand to hermes-ui and consolidate i18n to zh/en
Build / build (push) Canceled after 0s
NPM Lockfile Check / npm ci --ignore-scripts (push) Canceled after 0s
Playwright / e2e (push) Canceled after 0s

Removes the ekko / hermes-web-ui branding from the whole tree and drops
eight locale files, so the project presents one name and one language pair.

Data compatibility is deliberate, not accidental: DB filenames, the JWT
audience, and skill manifest ownership all still recognise their pre-rename
values so existing deployments under ~/.hermes-web-ui keep working without a
re-login or a re-injection.

scripts/harness-check.mjs now scans for the legacy strings and fails the
build on any new occurrence, with an explicit allow-list (and reasons) for
the compatibility paths that must keep them.
This commit is contained in:
2026-10-16 11:11:29 +08:00
parent 1e9ea89d86
commit c93e1fe1bd
70 changed files with 1305 additions and 17856 deletions
@@ -820,9 +820,9 @@ function isDesktopRuntime(): boolean {
function candidateBundledMcpScripts(): string[] {
return [
process.env.HERMES_WEB_UI_MCP_BIN,
join(process.cwd(), 'bin/hermes-studio-mcp.mjs'),
join(__dirname, '../../bin/hermes-studio-mcp.mjs'),
join(__dirname, '../../../../../bin/hermes-studio-mcp.mjs'),
join(process.cwd(), 'bin/hermes-ui-mcp.mjs'),
join(__dirname, '../../bin/hermes-ui-mcp.mjs'),
join(__dirname, '../../../../../bin/hermes-ui-mcp.mjs'),
join(process.cwd(), 'bin/hermes-web-ui-mcp.mjs'),
join(__dirname, '../../bin/hermes-web-ui-mcp.mjs'),
join(__dirname, '../../../../../bin/hermes-web-ui-mcp.mjs'),
@@ -44,6 +44,9 @@ function shouldIncludeManagedMcpServers(): boolean {
function candidateBundledMcpScripts(): string[] {
return [
process.env.HERMES_WEB_UI_MCP_BIN,
join(process.cwd(), 'bin/hermes-ui-mcp.mjs'),
join(__dirname, '../../bin/hermes-ui-mcp.mjs'),
join(__dirname, '../../../../../bin/hermes-ui-mcp.mjs'),
join(process.cwd(), 'bin/hermes-studio-mcp.mjs'),
join(__dirname, '../../bin/hermes-studio-mcp.mjs'),
join(__dirname, '../../../../../bin/hermes-studio-mcp.mjs'),
@@ -91,7 +91,7 @@ export async function fetchPetdexManifest(options: { force?: boolean } = {}): Pr
const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS)
try {
const response = await fetch(PETDEX_MANIFEST_URL, {
headers: { 'User-Agent': 'hermes-web-ui-petdex' },
headers: { 'User-Agent': 'hermes-ui-petdex' },
signal: controller.signal,
})
if (!response.ok) {
@@ -111,7 +111,7 @@ export async function fetchPetdexAsset(urlValue: string): Promise<{ buffer: Buff
const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS)
try {
const response = await fetch(url, {
headers: { 'User-Agent': 'hermes-web-ui-petdex' },
headers: { 'User-Agent': 'hermes-ui-petdex' },
signal: controller.signal,
})
if (!response.ok) throw new Error(`petdex asset request failed: ${response.status}`)
+1 -1
View File
@@ -133,7 +133,7 @@ async function fetchBytes(urlValue: string, maxBytes: number): Promise<{ buffer:
const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS)
try {
const response = await fetch(url, {
headers: { 'User-Agent': 'hermes-web-ui-pets' },
headers: { 'User-Agent': 'hermes-ui-pets' },
signal: controller.signal,
})
if (!response.ok) throw new Error(`Pet asset request failed: ${response.status}`)
@@ -6,7 +6,13 @@ import { detectHermesRootHome } from './hermes-path'
import { logger } from '../logger'
const MANIFEST_FILENAME = '.webui-managed-skills.json'
const MANIFEST_OWNER = 'hermes-web-ui'
const MANIFEST_OWNER = 'hermes-ui'
// Manifests written before the hermes-web-ui -> hermes-ui rename still carry the old
// owner. Recognising them keeps managed skills managed instead of re-injecting them.
const LEGACY_MANIFEST_OWNERS = new Set(['hermes-web-ui'])
function isManagedOwner(owner: string | undefined): boolean {
return owner === MANIFEST_OWNER || LEGACY_MANIFEST_OWNERS.has(String(owner))
}
const HASH_IGNORED_FILENAMES = new Set([
'.DS_Store',
'Thumbs.db',
@@ -177,7 +183,7 @@ export class HermesSkillInjector {
const currentHash = await this.hashDir(targetSkillDir)
const manifestEntry = manifest[entry.name]
const isManaged = manifestEntry?.owner === MANIFEST_OWNER
const isManaged = isManagedOwner(manifestEntry?.owner)
const isUnchangedManagedCopy = isManaged && manifestEntry?.installed_hash === currentHash
const isExistingBundledCopy = !manifestEntry && currentHash === sourceHash
@@ -5,7 +5,7 @@ import { SttNoSpeechDetectedError, type SttTranscribeInput, type SttTranscribeRe
const DEFAULT_BASE_URL = 'https://openspeech.bytedance.com/api/v3/auc/bigmodel'
const DEFAULT_RESOURCE_ID = 'volc.seedasr.auc'
const DEFAULT_USER_ID = 'hermes-web-ui'
const DEFAULT_USER_ID = 'hermes-ui'
const DONE_STATUS = '20000000'
const RUNNING_STATUSES = new Set(['20000001', '20000002'])
const STATUS_MESSAGES: Record<string, string> = {
@@ -75,9 +75,9 @@ function isDesktopRuntime(): boolean {
function candidateBundledMcpScripts(): string[] {
return [
process.env.HERMES_WEB_UI_MCP_BIN,
join(process.cwd(), 'bin/hermes-studio-mcp.mjs'),
join(__dirname, '../../bin/hermes-studio-mcp.mjs'),
join(__dirname, '../../../../../bin/hermes-studio-mcp.mjs'),
join(process.cwd(), 'bin/hermes-ui-mcp.mjs'),
join(__dirname, '../../bin/hermes-ui-mcp.mjs'),
join(__dirname, '../../../../../bin/hermes-ui-mcp.mjs'),
join(process.cwd(), 'bin/hermes-web-ui-mcp.mjs'),
join(__dirname, '../../bin/hermes-web-ui-mcp.mjs'),
join(__dirname, '../../../../../bin/hermes-web-ui-mcp.mjs'),
@@ -6,7 +6,7 @@ import { assertSafeTtsBaseUrl } from './url-safety'
const DEFAULT_BASE_URL = 'https://openspeech.bytedance.com/api/v3/tts/unidirectional'
const DEFAULT_RESOURCE_ID = 'seed-tts-2.0'
const DEFAULT_SPEAKER = 'zh_female_xiaohe_uranus_bigtts'
const DEFAULT_USER_ID = 'hermes-web-ui'
const DEFAULT_USER_ID = 'hermes-ui'
const MAX_ERROR_DETAIL_LENGTH = 500
const DEFAULT_FORMAT = 'mp3'
const DEFAULT_SAMPLE_RATE = 24000
+1 -1
View File
@@ -8,7 +8,7 @@ const MAX_LOG_SIZE = 3 * 1024 * 1024 // 3MB
const CHECK_INTERVAL = 60_000 // Check every minute
const logDir = process.env.VITEST
? resolve(tmpdir(), 'hermes-web-ui-test-logs', String(process.pid))
? resolve(tmpdir(), 'hermes-ui-test-logs', String(process.pid))
: resolve(config.appHome, 'logs')
mkdirSync(logDir, { recursive: true })
@@ -400,8 +400,8 @@ export async function downloadWebUiVersion(version: string, source: VersionDownl
if (!cleanVersion) throw new Error('Web UI version is required')
const releaseTag = `v${cleanVersion}`
const assetName = `hermes-web-ui-${cleanVersion}.tar.gz`
const manifestName = `hermes-web-ui-${cleanVersion}.json`
const assetName = `hermes-ui-${cleanVersion}.tar.gz`
const manifestName = `hermes-ui-${cleanVersion}.json`
const manifestUrl = downloadAssetUrl(manifestName, releaseTag, source)
onProgress?.({ stage: 'resolve', message: 'runtimeVersions.jobStage.resolveWebUi' })
const manifest = await fetchJson<{ asset?: { sha256?: string; size?: number } }>(manifestUrl)
@@ -424,7 +424,7 @@ export async function downloadWebUiVersion(version: string, source: VersionDownl
onProgress?.({ stage: 'extract', message: 'runtimeVersions.jobStage.extractWebUi' })
await extractTarGzip(archive, tempRoot)
const extractedRoot = join(tempRoot, 'webui')
for (const required of ['package.json', 'bin/hermes-web-ui.mjs', 'dist/server/index.js']) {
for (const required of ['package.json', 'bin/hermes-ui.mjs', 'dist/server/index.js']) {
if (!existsSync(join(extractedRoot, required))) throw new Error(`Web UI archive is missing required file: ${required}`)
}
onProgress?.({ stage: 'install', message: 'runtimeVersions.jobStage.installWebUi' })