mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-01 12:02:05 +00:00
style(desktop): prettier + eslint pass
Repo-wide `npm run fmt` + `eslint --fix`; also drop two unused destructured params in titlebar-overlay-width.cjs so the lint run is clean.
This commit is contained in:
parent
317b94871b
commit
9f02eea1d2
16 changed files with 74 additions and 64 deletions
|
|
@ -2266,9 +2266,7 @@ async function handOffWindowsBootstrapRecovery(reason) {
|
|||
// --repair (full venv recreate) and drove reinstall loops. The venv interpreter
|
||||
// and the bootstrap-complete marker are present earlier and are better signals.
|
||||
const haveRealInstall =
|
||||
fileExists(venvPython) ||
|
||||
fileExists(venvHermes) ||
|
||||
fileExists(path.join(updateRoot, '.hermes-bootstrap-complete'))
|
||||
fileExists(venvPython) || fileExists(venvHermes) || fileExists(path.join(updateRoot, '.hermes-bootstrap-complete'))
|
||||
const updaterArgs = haveRealInstall ? ['--update', '--branch', branch] : ['--repair', '--branch', branch]
|
||||
|
||||
await releaseBackendLockForUpdate(updateRoot)
|
||||
|
|
|
|||
|
|
@ -14,9 +14,9 @@ const OVERLAY_FALLBACK_WIDTH = 144
|
|||
* the Electron overlay (Windows, WSLg, and plain Linux KDE/GNOME), so they all
|
||||
* reserve the fallback width.
|
||||
*
|
||||
* @param {{ isWindows?: boolean, isWsl?: boolean, isMac?: boolean }} opts
|
||||
* @param {{ isMac?: boolean }} opts
|
||||
*/
|
||||
function nativeOverlayWidth({ isWindows = false, isWsl = false, isMac = false } = {}) {
|
||||
function nativeOverlayWidth({ isMac = false } = {}) {
|
||||
if (isMac) return 0
|
||||
return OVERLAY_FALLBACK_WIDTH
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,21 +43,13 @@ test('findOnPath tries PATHEXT extensions before the bare (empty) name on Window
|
|||
test('Windows bootstrap recovery chooses --update when any real-install signal is present', () => {
|
||||
const source = readMain()
|
||||
assert.match(source, /const haveRealInstall =/, 'recovery must compute haveRealInstall')
|
||||
assert.match(
|
||||
source,
|
||||
/fileExists\(venvPython\)/,
|
||||
'recovery must accept the venv interpreter as a real-install signal'
|
||||
)
|
||||
assert.match(source, /fileExists\(venvPython\)/, 'recovery must accept the venv interpreter as a real-install signal')
|
||||
assert.match(
|
||||
source,
|
||||
/\.hermes-bootstrap-complete/,
|
||||
'recovery must accept the bootstrap-complete marker as a real-install signal'
|
||||
)
|
||||
assert.match(
|
||||
source,
|
||||
/updaterArgs = haveRealInstall \? \['--update'/,
|
||||
'updaterArgs must gate on haveRealInstall'
|
||||
)
|
||||
assert.match(source, /updaterArgs = haveRealInstall \? \['--update'/, 'updaterArgs must gate on haveRealInstall')
|
||||
// The old too-narrow check (only venv\Scripts\hermes.exe) must not return.
|
||||
assert.doesNotMatch(
|
||||
source,
|
||||
|
|
|
|||
|
|
@ -1149,8 +1149,7 @@ export function ChatSidebar({
|
|||
|
||||
const showSessionSkeletons = sessionsLoading && sortedSessions.length === 0
|
||||
|
||||
const showSessionSections =
|
||||
showSessionSkeletons || sortedSessions.length > 0 || projectModel.length > 0
|
||||
const showSessionSections = showSessionSkeletons || sortedSessions.length > 0 || projectModel.length > 0
|
||||
|
||||
// Each reorderable list reports its OWN new id order; persisting is a direct,
|
||||
// typed write — no id-prefix sniffing to figure out which level moved.
|
||||
|
|
@ -1628,12 +1627,7 @@ function SidebarBlankState({ onNewProject }: { onNewProject: () => void }) {
|
|||
<div className="flex flex-col items-center gap-2">
|
||||
<Codicon className="text-(--ui-text-quaternary)" name="root-folder" size="1.25rem" />
|
||||
<p className="text-xs text-(--ui-text-tertiary)">{s.noSessions}</p>
|
||||
<Button
|
||||
className="mt-0.5 text-(--ui-text-secondary)"
|
||||
onClick={onNewProject}
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
>
|
||||
<Button className="mt-0.5 text-(--ui-text-secondary)" onClick={onNewProject} size="sm" variant="ghost">
|
||||
<Codicon name="add" size="0.75rem" />
|
||||
{s.projects.newButton}
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -149,10 +149,7 @@ export function ProjectDialog() {
|
|||
|
||||
return (
|
||||
<Dialog onOpenChange={onOpenChange} open={open}>
|
||||
<DialogContent
|
||||
className="max-w-md"
|
||||
onInteractOutside={event => event.preventDefault()}
|
||||
>
|
||||
<DialogContent className="max-w-md" onInteractOutside={event => event.preventDefault()}>
|
||||
<DialogHeader>
|
||||
<DialogTitle>{title}</DialogTitle>
|
||||
{mode === 'create' && <DialogDescription>{p.createDesc}</DialogDescription>}
|
||||
|
|
|
|||
|
|
@ -596,7 +596,6 @@ function CronJobDetail({
|
|||
)
|
||||
}
|
||||
|
||||
|
||||
function formatRunTime(seconds?: null | number): string {
|
||||
if (!seconds) {
|
||||
return '—'
|
||||
|
|
|
|||
|
|
@ -2,12 +2,7 @@ import type { ReactNode } from 'react'
|
|||
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Codicon } from '@/components/ui/codicon'
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger
|
||||
} from '@/components/ui/dropdown-menu'
|
||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@/components/ui/dropdown-menu'
|
||||
import { SearchField } from '@/components/ui/search-field'
|
||||
import { translateNow } from '@/i18n'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
|
@ -146,7 +141,17 @@ interface PanelListRowProps {
|
|||
// A row is a container (not a <button>) so it can host both the select target
|
||||
// and a kebab menu without nesting interactive elements. Hover/active bg lives
|
||||
// on the wrapper so the whole row highlights as one.
|
||||
export function PanelListRow({ active, dotClassName, icon, lead, menu, meta, onSelect, rowKey, title }: PanelListRowProps) {
|
||||
export function PanelListRow({
|
||||
active,
|
||||
dotClassName,
|
||||
icon,
|
||||
lead,
|
||||
menu,
|
||||
meta,
|
||||
onSelect,
|
||||
rowKey,
|
||||
title
|
||||
}: PanelListRowProps) {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
|
|
@ -162,11 +167,12 @@ export function PanelListRow({ active, dotClassName, icon, lead, menu, meta, onS
|
|||
onClick={onSelect}
|
||||
type="button"
|
||||
>
|
||||
{lead ?? (dotClassName ? (
|
||||
<span aria-hidden="true" className={cn('size-1.5 shrink-0 rounded-full', dotClassName)} />
|
||||
) : icon ? (
|
||||
<Codicon className="shrink-0 text-muted-foreground/55" name={icon} size="0.85rem" />
|
||||
) : null)}
|
||||
{lead ??
|
||||
(dotClassName ? (
|
||||
<span aria-hidden="true" className={cn('size-1.5 shrink-0 rounded-full', dotClassName)} />
|
||||
) : icon ? (
|
||||
<Codicon className="shrink-0 text-muted-foreground/55" name={icon} size="0.85rem" />
|
||||
) : null)}
|
||||
<span className="min-w-0 flex-1 truncate font-medium text-foreground/85">{title}</span>
|
||||
</button>
|
||||
{meta ? <span className="shrink-0 pr-2 text-[0.62rem] tabular-nums text-muted-foreground/45">{meta}</span> : null}
|
||||
|
|
@ -321,7 +327,15 @@ export function PanelPill({ children, tone = 'muted' }: { children: ReactNode; t
|
|||
|
||||
// Self-describing centered "+" that sits as the LAST item in a PanelList. The
|
||||
// label rides aria/title only — no visible text.
|
||||
export function PanelAddButton({ icon = 'add', label, onClick }: { icon?: string; label: string; onClick: () => void }) {
|
||||
export function PanelAddButton({
|
||||
icon = 'add',
|
||||
label,
|
||||
onClick
|
||||
}: {
|
||||
icon?: string
|
||||
label: string
|
||||
onClick: () => void
|
||||
}) {
|
||||
return (
|
||||
<Button
|
||||
aria-label={label}
|
||||
|
|
|
|||
|
|
@ -295,7 +295,13 @@ function ProfileRow({
|
|||
return (
|
||||
<PanelListRow
|
||||
active={active}
|
||||
lead={<ProfileGlyph color={resolveProfileColor(profile.name, colors)} isDefault={profile.is_default} name={profile.name} />}
|
||||
lead={
|
||||
<ProfileGlyph
|
||||
color={resolveProfileColor(profile.name, colors)}
|
||||
isDefault={profile.is_default}
|
||||
name={profile.name}
|
||||
/>
|
||||
}
|
||||
menu={menu}
|
||||
onSelect={onSelect}
|
||||
rowKey={profile.name}
|
||||
|
|
@ -313,7 +319,12 @@ function ProfileGlyph({ color, isDefault, name }: { color: null | string; isDefa
|
|||
}
|
||||
|
||||
const hue = color ?? 'var(--ui-text-quaternary)'
|
||||
const initial = name.replace(/[^a-z0-9]/gi, '').charAt(0).toUpperCase() || '?'
|
||||
|
||||
const initial =
|
||||
name
|
||||
.replace(/[^a-z0-9]/gi, '')
|
||||
.charAt(0)
|
||||
.toUpperCase() || '?'
|
||||
|
||||
return (
|
||||
<span
|
||||
|
|
@ -367,7 +378,6 @@ function ProfileDetail({ profile }: { profile: ProfileInfo }) {
|
|||
)
|
||||
}
|
||||
|
||||
|
||||
function SoulEditor({ profileName }: { profileName: string }) {
|
||||
const { t } = useI18n()
|
||||
const p = t.profiles
|
||||
|
|
|
|||
|
|
@ -685,7 +685,9 @@ export function useSessionActions({
|
|||
if (warmHit) {
|
||||
const cachedRuntimeId = warmHit.runtimeId
|
||||
const cachedState = warmHit.state
|
||||
const stored = $sessions.get().find(session => sessionMatchesStoredId(session, storedSessionId)) ?? storedForProfile
|
||||
|
||||
const stored =
|
||||
$sessions.get().find(session => sessionMatchesStoredId(session, storedSessionId)) ?? storedForProfile
|
||||
|
||||
const cachedViewState =
|
||||
!cachedState.model && stored?.model != null
|
||||
|
|
@ -752,7 +754,10 @@ export function useSessionActions({
|
|||
setSelectedStoredSessionId(storedSessionId)
|
||||
selectedStoredSessionIdRef.current = storedSessionId
|
||||
setSessionStartedAt(Date.now())
|
||||
const stored = $sessions.get().find(session => sessionMatchesStoredId(session, storedSessionId)) ?? storedForProfile
|
||||
|
||||
const stored =
|
||||
$sessions.get().find(session => sessionMatchesStoredId(session, storedSessionId)) ?? storedForProfile
|
||||
|
||||
applyStoredSessionPreviewRuntimeInfo(stored)
|
||||
|
||||
if (stored) {
|
||||
|
|
|
|||
|
|
@ -191,9 +191,7 @@ export function ModelSettings({ onMainModelChanged }: ModelSettingsProps) {
|
|||
// MoA reference/aggregator slots must never be the moa virtual provider —
|
||||
// that would create a recursive MoA tree (the backend rejects it on save).
|
||||
// Hide it from the slot selectors so it isn't offered as a dead choice.
|
||||
const moaSlotProviderOptions = providerOptions.filter(
|
||||
provider => (provider.slug || '').toLowerCase() !== 'moa'
|
||||
)
|
||||
const moaSlotProviderOptions = providerOptions.filter(provider => (provider.slug || '').toLowerCase() !== 'moa')
|
||||
|
||||
const selectedProviderRow = useMemo(
|
||||
() => providers.find(provider => provider.slug === selectedProvider),
|
||||
|
|
@ -783,6 +781,7 @@ export function ModelSettings({ onMainModelChanged }: ModelSettingsProps) {
|
|||
...moa,
|
||||
default_preset: selectedMoaPreset || moa.default_preset
|
||||
}
|
||||
|
||||
void saveMoa(next)
|
||||
}}
|
||||
size="sm"
|
||||
|
|
@ -800,12 +799,14 @@ export function ModelSettings({ onMainModelChanged }: ModelSettingsProps) {
|
|||
const presets = { ...moa.presets }
|
||||
delete presets[selectedMoaPreset]
|
||||
const fallback = Object.keys(presets)[0]
|
||||
|
||||
const next: MoaConfigResponse = {
|
||||
...moa,
|
||||
presets,
|
||||
default_preset: moa.default_preset === selectedMoaPreset ? fallback : moa.default_preset,
|
||||
active_preset: moa.active_preset === selectedMoaPreset ? '' : moa.active_preset
|
||||
}
|
||||
|
||||
setSelectedMoaPreset(Object.keys(moa.presets).find(name => name !== selectedMoaPreset) || '')
|
||||
void saveMoa(next)
|
||||
}}
|
||||
|
|
@ -824,6 +825,7 @@ export function ModelSettings({ onMainModelChanged }: ModelSettingsProps) {
|
|||
disabled={!newMoaPresetName.trim() || !!moa.presets[newMoaPresetName.trim()] || applying}
|
||||
onClick={() => {
|
||||
const name = newMoaPresetName.trim()
|
||||
|
||||
const next: MoaConfigResponse = {
|
||||
...moa,
|
||||
presets: {
|
||||
|
|
@ -831,6 +833,7 @@ export function ModelSettings({ onMainModelChanged }: ModelSettingsProps) {
|
|||
[name]: { ...currentMoaPreset, reference_models: [...currentMoaPreset.reference_models] }
|
||||
}
|
||||
}
|
||||
|
||||
setSelectedMoaPreset(name)
|
||||
setNewMoaPresetName('')
|
||||
void saveMoa(next)
|
||||
|
|
|
|||
|
|
@ -9,10 +9,7 @@ describe('withActive', () => {
|
|||
const curated = ['hermes-4', 'hermes-4-mini']
|
||||
|
||||
it('prepends a custom model missing from the curated list', () => {
|
||||
expect(withActive(curated, 'anthropic/claude-opus-4.7')).toEqual([
|
||||
'anthropic/claude-opus-4.7',
|
||||
...curated
|
||||
])
|
||||
expect(withActive(curated, 'anthropic/claude-opus-4.7')).toEqual(['anthropic/claude-opus-4.7', ...curated])
|
||||
})
|
||||
|
||||
it('leaves the list untouched when the active model is already curated', () => {
|
||||
|
|
|
|||
|
|
@ -235,6 +235,7 @@ export function useStatusbarItems({
|
|||
const applying = backendUpdateApply.applying || backendUpdateApply.stage === 'restart'
|
||||
|
||||
const base = copy.backendLabel(backendVersion ?? copy.unknown)
|
||||
|
||||
const behindHint =
|
||||
!applying && behind > 0 ? ` (+${behind})` : !applying && updateAvailable ? ` (${copy.update})` : ''
|
||||
|
||||
|
|
|
|||
|
|
@ -280,7 +280,11 @@ function ClarifyToolPending({ args }: ToolCallMessagePartProps) {
|
|||
|
||||
if (loading) {
|
||||
return (
|
||||
<ClarifyShell aria-label={copy.loadingQuestion} className="grid min-h-12 place-items-center px-2.5 py-3" role="status">
|
||||
<ClarifyShell
|
||||
aria-label={copy.loadingQuestion}
|
||||
className="grid min-h-12 place-items-center px-2.5 py-3"
|
||||
role="status"
|
||||
>
|
||||
<Loader2 aria-hidden className="size-4 animate-spin text-(--ui-text-tertiary)" />
|
||||
</ClarifyShell>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1540,10 +1540,7 @@ function dynamicTitle(
|
|||
}
|
||||
|
||||
const failed =
|
||||
part.isError ||
|
||||
result.success === false ||
|
||||
result.ok === false ||
|
||||
Boolean(firstStringField(result, ['error']))
|
||||
part.isError || result.success === false || result.ok === false || Boolean(firstStringField(result, ['error']))
|
||||
|
||||
if (failed) {
|
||||
const failAction = translateNow('assistant.tool.actions.failedToOpen')
|
||||
|
|
@ -1556,10 +1553,7 @@ function dynamicTitle(
|
|||
|
||||
const action = verb(translateNow('assistant.tool.actions.opening'), translateNow('assistant.tool.actions.opened'))
|
||||
|
||||
return titledAction(
|
||||
action,
|
||||
translateNow('assistant.tool.titleTemplates.actionTarget', action, hostnameOf(url))
|
||||
)
|
||||
return titledAction(action, translateNow('assistant.tool.titleTemplates.actionTarget', action, hostnameOf(url)))
|
||||
}
|
||||
|
||||
if (part.toolName === 'web_search') {
|
||||
|
|
|
|||
|
|
@ -279,7 +279,8 @@ export const zhHant = defineLocale({
|
|||
translucencyTitle: '視窗透明',
|
||||
translucencyDesc: '讓整個視窗透出桌面。僅支援 macOS 與 Windows。',
|
||||
embedsTitle: '內嵌預覽',
|
||||
embedsDesc: '豐富預覽會從第三方網站(YouTube、X 等)載入。詢問會在你允許前顯示佔位符;一律會自動載入;關閉則保留純連結。',
|
||||
embedsDesc:
|
||||
'豐富預覽會從第三方網站(YouTube、X 等)載入。詢問會在你允許前顯示佔位符;一律會自動載入;關閉則保留純連結。',
|
||||
embedsAsk: '詢問',
|
||||
embedsAlways: '一律',
|
||||
embedsOff: '關閉',
|
||||
|
|
|
|||
|
|
@ -370,7 +370,8 @@ export const zh: Translations = {
|
|||
translucencyTitle: '窗口透明',
|
||||
translucencyDesc: '让整个窗口透出桌面。仅支持 macOS 和 Windows。',
|
||||
embedsTitle: '内嵌预览',
|
||||
embedsDesc: '富预览会从第三方网站(YouTube、X 等)加载。询问会在你允许前显示占位符;总是会自动加载;关闭则保留纯链接。',
|
||||
embedsDesc:
|
||||
'富预览会从第三方网站(YouTube、X 等)加载。询问会在你允许前显示占位符;总是会自动加载;关闭则保留纯链接。',
|
||||
embedsAsk: '询问',
|
||||
embedsAlways: '总是',
|
||||
embedsOff: '关闭',
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue