+
Couldn’t launch the desktop app
{error}
diff --git a/apps/bootstrap-installer/src/routes/welcome.tsx b/apps/bootstrap-installer/src/routes/welcome.tsx
index c09080cfcfb6..73bfd9bf7343 100644
--- a/apps/bootstrap-installer/src/routes/welcome.tsx
+++ b/apps/bootstrap-installer/src/routes/welcome.tsx
@@ -1,4 +1,5 @@
import { type CSSProperties } from 'react'
+
import { HackeryButton } from '../components/hackery-button'
import { startInstall } from '../store'
diff --git a/apps/bootstrap-installer/src/store.ts b/apps/bootstrap-installer/src/store.ts
index d2235886781b..e2a9522bfb27 100644
--- a/apps/bootstrap-installer/src/store.ts
+++ b/apps/bootstrap-installer/src/store.ts
@@ -1,6 +1,6 @@
-import { atom, computed } from 'nanostores'
-import { listen, type UnlistenFn } from '@tauri-apps/api/event'
import { invoke } from '@tauri-apps/api/core'
+import { listen, type UnlistenFn } from '@tauri-apps/api/event'
+import { atom, computed } from 'nanostores'
/*
* Bootstrap state store — single source of truth for installer screens.
@@ -79,12 +79,16 @@ export const $hermesHome = atom
(null)
export const $progress = computed($bootstrap, (b) => {
const total = b.stageOrder.length
- if (total === 0) return { done: 0, total: 0, fraction: 0 }
+
+ if (total === 0) {return { done: 0, total: 0, fraction: 0 }}
let done = 0
+
for (const name of b.stageOrder) {
const s = b.stages[name]?.state
- if (s === 'succeeded' || s === 'skipped' || s === 'failed') done += 1
+
+ if (s === 'succeeded' || s === 'skipped' || s === 'failed') {done += 1}
}
+
return { done, total, fraction: done / total }
})
@@ -99,7 +103,9 @@ function withStageState(
error?: string
): BootstrapStateModel {
const existing = cur.stages[name]
- if (!existing) return cur
+
+ if (!existing) {return cur}
+
return {
...cur,
stages: {
@@ -163,18 +169,21 @@ type BootstrapEvent =
let unlisten: UnlistenFn | null = null
export async function initialize(): Promise {
- if (unlisten) return
+ if (unlisten) {return}
// Dev-only isolated preview (see runFakeBoot): drive the screens in a plain
// browser, no Tauri backend, no real install.
const fake = fakeMode()
+
if (fake) {
unlisten = () => {}
$logPath.set('~/.hermes/logs/bootstrap-installer.log')
$hermesHome.set('~/.hermes')
$mode.set(fake === 'update' ? 'update' : 'install')
+
// Update auto-runs (it's a hand-off); install/failure wait for the welcome click.
- if (fake === 'update') void runFakeBoot('update')
+ if (fake === 'update') {void runFakeBoot('update')}
+
return
}
@@ -185,6 +194,7 @@ export async function initialize(): Promise {
invoke('get_hermes_home'),
invoke('get_mode')
])
+
$logPath.set(logPath)
$hermesHome.set(hermesHome)
$mode.set(mode)
@@ -195,14 +205,17 @@ export async function initialize(): Promise {
unlisten = await listen('bootstrap', (event) => {
const payload = event.payload
const cur = $bootstrap.get()
+
switch (payload.type) {
case 'manifest': {
const stages: Record = {}
const order: string[] = []
+
for (const s of payload.stages) {
stages[s.name] = { info: s, state: null }
order.push(s.name)
}
+
$bootstrap.set({
...cur,
status: 'running',
@@ -215,26 +228,34 @@ export async function initialize(): Promise {
logs: []
})
$route.set('progress')
+
break
}
+
case 'stage': {
if (!cur.stages[payload.name]) {
console.warn('stage event for unknown stage', payload.name)
+
break
}
+
$bootstrap.set(
withStageState(cur, payload.name, payload.state, payload.durationMs, payload.error)
)
+
break
}
+
case 'log': {
const logs = [...cur.logs, { stage: payload.stage, line: payload.line, stream: payload.stream }]
// Keep the rolling buffer bounded so the UI doesn't get OOM'd
// during a long install (playwright chromium download is ~10k lines).
const trimmed = logs.length > 2000 ? logs.slice(-2000) : logs
$bootstrap.set({ ...cur, logs: trimmed })
+
break
}
+
case 'complete':
$bootstrap.set({
...cur,
@@ -242,6 +263,7 @@ export async function initialize(): Promise {
installRoot: payload.installRoot,
currentStage: null
})
+
// Install: show the "launch Hermes" success screen. Update: this is a
// hand-off — the installer relaunches the desktop and exits within a
// few hundred ms, so routing to success just flashes that screen
@@ -249,7 +271,9 @@ export async function initialize(): Promise {
if ($mode.get() !== 'update') {
$route.set('success')
}
+
break
+
case 'failed':
$bootstrap.set({
...cur,
@@ -258,6 +282,7 @@ export async function initialize(): Promise {
currentStage: null
})
$route.set('failure')
+
break
}
})
@@ -276,10 +301,13 @@ export async function initialize(): Promise {
export async function startInstall(opts?: { branch?: string }): Promise {
const fake = fakeMode()
+
if (fake) {
void runFakeBoot(fake === 'failure' ? 'failure' : 'install')
+
return
}
+
// Reset before kicking off so a retry from the failure screen clears
// the previous run's state.
$bootstrap.set(INITIAL)
@@ -297,8 +325,10 @@ export async function startInstall(opts?: { branch?: string }): Promise {
export async function startUpdate(): Promise {
if (fakeMode()) {
void runFakeBoot('update')
+
return
}
+
// Update is driven by the desktop handing off (Hermes-Setup.exe --update);
// there's no welcome click. Reset + jump straight to progress, then let the
// Rust side stream the synthetic update manifest.
@@ -310,20 +340,23 @@ export async function startUpdate(): Promise {
export async function cancelInstall(): Promise {
if (fakeMode()) {
fakeCancelled = true
+
return
}
+
await invoke('cancel_bootstrap')
}
export async function launchHermesDesktop(): Promise {
- if (fakeMode()) throw new Error('Preview mode — launching is disabled.')
+ if (fakeMode()) {throw new Error('Preview mode — launching is disabled.')}
const installRoot = $bootstrap.get().installRoot
- if (!installRoot) throw new Error('no install root')
+
+ if (!installRoot) {throw new Error('no install root')}
await invoke('launch_hermes_desktop', { installRoot })
}
export async function openLogDir(): Promise {
- if (fakeMode()) return
+ if (fakeMode()) {return}
await invoke('open_log_dir')
}
@@ -341,8 +374,9 @@ export async function openLogDir(): Promise {
type FakeMode = 'install' | 'update' | 'failure'
function fakeMode(): FakeMode | null {
- if (!import.meta.env.DEV || typeof window === 'undefined') return null
+ if (!import.meta.env.DEV || typeof window === 'undefined') {return null}
const v = new URLSearchParams(window.location.search).get('fake')
+
return v === 'install' || v === 'update' || v === 'failure' ? v : null
}
@@ -383,15 +417,18 @@ const fakeFail = (error: string) =>
$bootstrap.set({ ...$bootstrap.get(), status: 'failed', error, currentStage: null })
async function runFakeBoot(kind: FakeMode): Promise {
- if (fakeRunning) return
+ if (fakeRunning) {return}
fakeRunning = true
fakeCancelled = false
+
try {
const stages = kind === 'update' ? FAKE_UPDATE_STAGES : FAKE_INSTALL_STAGES
+
const cancelled = () => {
- if (!fakeCancelled) return false
+ if (!fakeCancelled) {return false}
fakeFail(kind === 'update' ? 'Update cancelled.' : 'Install cancelled.')
$route.set('failure')
+
return true
}
@@ -412,14 +449,16 @@ async function runFakeBoot(kind: FakeMode): Promise {
const failAt = kind === 'failure' ? stages[Math.floor(stages.length / 2)]?.name : null
for (const s of stages) {
- if (cancelled()) return
+ if (cancelled()) {return}
fakeStage(s.name, 'running')
const durationMs = 700 + Math.floor(Math.random() * 2200)
const lines = Math.max(2, Math.round(durationMs / 450))
+
for (let l = 0; l < lines; l++) {
await sleep(durationMs / lines)
- if (cancelled()) return
+
+ if (cancelled()) {return}
fakeLog(s.name, `[${s.name}] ${s.title.toLowerCase()} — step ${l + 1}/${lines}…`)
}
@@ -427,15 +466,18 @@ async function runFakeBoot(kind: FakeMode): Promise {
fakeStage(s.name, 'failed', durationMs, 'Simulated failure for preview.')
fakeFail('Simulated failure for preview (fake boot).')
$route.set('failure')
+
return
}
+
fakeStage(s.name, 'succeeded', durationMs)
}
$bootstrap.set({ ...$bootstrap.get(), status: 'completed', currentStage: null })
+
// Install lands on success; update stays on progress (the real updater
// relaunches the desktop and exits from there).
- if (kind !== 'update') $route.set('success')
+ if (kind !== 'update') {$route.set('success')}
} finally {
fakeRunning = false
}
diff --git a/apps/desktop/electron/bootstrap-runner.test.ts b/apps/desktop/electron/bootstrap-runner.test.ts
index eaffcac3ae08..d23704cb30d7 100644
--- a/apps/desktop/electron/bootstrap-runner.test.ts
+++ b/apps/desktop/electron/bootstrap-runner.test.ts
@@ -87,16 +87,7 @@ test('fresh bootstrap args include the packaged commit pin', () => {
activeRoot: '/tmp/hermes-agent',
hermesHome: '/tmp/hermes'
}),
- [
- '--dir',
- '/tmp/hermes-agent',
- '--hermes-home',
- '/tmp/hermes',
- '--branch',
- 'main',
- '--commit',
- installStamp.commit
- ]
+ ['--dir', '/tmp/hermes-agent', '--hermes-home', '/tmp/hermes', '--branch', 'main', '--commit', installStamp.commit]
)
})
diff --git a/apps/desktop/electron/bootstrap-runner.ts b/apps/desktop/electron/bootstrap-runner.ts
index 284dcfeeb0b1..47f76774b427 100644
--- a/apps/desktop/electron/bootstrap-runner.ts
+++ b/apps/desktop/electron/bootstrap-runner.ts
@@ -573,15 +573,7 @@ function buildPosixPinArgs({ installStamp, activeRoot, hermesHome, pinCommit = t
return args
}
-async function fetchManifest({
- scriptPath,
- installerKind,
- emit,
- hermesHome,
- activeRoot,
- installStamp,
- pinCommit
-}) {
+async function fetchManifest({ scriptPath, installerKind, emit, hermesHome, activeRoot, installStamp, pinCommit }) {
const isPosix = installerKind === 'posix'
const args = isPosix
diff --git a/apps/desktop/electron/connection-config.test.ts b/apps/desktop/electron/connection-config.test.ts
index e28f6959dab2..425e63b15f21 100644
--- a/apps/desktop/electron/connection-config.test.ts
+++ b/apps/desktop/electron/connection-config.test.ts
@@ -110,6 +110,7 @@ test('profileRemoteOverride treats a cloud entry as a remote override', () => {
coder: { mode: 'cloud', url: 'https://agent-1.agents.nousresearch.com', authMode: 'oauth' }
}
}
+
assert.deepEqual(profileRemoteOverride(config, 'coder'), {
url: 'https://agent-1.agents.nousresearch.com',
authMode: 'oauth',
diff --git a/apps/desktop/electron/desktop-electron-pin.test.ts b/apps/desktop/electron/desktop-electron-pin.test.ts
index d94ab570ce3c..d648409b353b 100644
--- a/apps/desktop/electron/desktop-electron-pin.test.ts
+++ b/apps/desktop/electron/desktop-electron-pin.test.ts
@@ -39,6 +39,7 @@ const EXACT_SEMVER = /^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/
function desktopPkg(): Record {
assert.ok(fs.existsSync(DESKTOP_PKG), `missing ${DESKTOP_PKG}`)
+
return JSON.parse(fs.readFileSync(DESKTOP_PKG, 'utf-8'))
}
@@ -46,8 +47,12 @@ function electronSpec(pkg: Record): string {
for (const section of ['dependencies', 'devDependencies'] as const) {
const deps = (pkg[section] ?? {}) as Record
const spec = deps['electron']
- if (spec) return spec
+
+ if (spec) {
+ return spec
+ }
}
+
assert.fail('electron is not listed in apps/desktop dependencies')
}
@@ -78,15 +83,20 @@ test('electron dependency matches build.electronVersion', () => {
})
test('lockfile resolves the pinned electron', () => {
- if (!fs.existsSync(ROOT_LOCK)) return // skip if lockfile not present
+ if (!fs.existsSync(ROOT_LOCK)) {
+ return
+ } // skip if lockfile not present
const spec = electronSpec(desktopPkg())
const lock = JSON.parse(fs.readFileSync(ROOT_LOCK, 'utf-8'))
const packages = (lock.packages ?? {}) as Record
+
const resolved = Object.entries(packages)
.filter(([key]) => key.endsWith('node_modules/electron'))
.map(([, meta]) => meta.version)
.filter((v): v is string => !!v)
+
assert.ok(resolved.length > 0, 'no electron entry found in package-lock.json')
+
for (const v of resolved) {
assert.equal(
v,
diff --git a/apps/desktop/electron/git-worktree-ops.test.ts b/apps/desktop/electron/git-worktree-ops.test.ts
index 63378c293586..e63e756ba6f1 100644
--- a/apps/desktop/electron/git-worktree-ops.test.ts
+++ b/apps/desktop/electron/git-worktree-ops.test.ts
@@ -227,7 +227,10 @@ test('listBaseBranches: lists local branches and flags the default', async () =>
assert.deepEqual(names, [trunk, 'feature'].sort())
// No remote → all local.
- assert.equal(branches.every(b => !b.isRemote), true)
+ assert.equal(
+ branches.every(b => !b.isRemote),
+ true
+ )
// The trunk is flagged as the default.
assert.equal(branches.find(b => b.name === trunk).isDefault, true)
assert.equal(branches.find(b => b.name === 'feature').isDefault, false)
@@ -254,7 +257,11 @@ test('addWorktree: base param branches off a specified local branch', async () =
await ensureGitRepo('git', dir)
execFileSync('git', ['branch', 'staging'], { cwd: dir })
- const result = await addWorktree(dir, { base: 'staging', branch: 'new-from-staging', name: 'new-from-staging' }, 'git')
+ const result = await addWorktree(
+ dir,
+ { base: 'staging', branch: 'new-from-staging', name: 'new-from-staging' },
+ 'git'
+ )
assert.equal(result.branch, 'new-from-staging')
assert.equal(git('-C', result.path, 'merge-base', 'HEAD', 'staging').length > 0, true)
@@ -274,12 +281,27 @@ test('addWorktree: base origin/main does not set up upstream tracking', async ()
// Seed the remote with a commit on main. Inline identity so it works
// on CI runners with no global git config.
execFileSync('git', ['init', '-b', 'main', remoteDir])
- execFileSync('git', ['-C', remoteDir, '-c', 'user.email=hermes@localhost', '-c', 'user.name=Hermes', 'commit', '--allow-empty', '-m', 'root'])
+ execFileSync('git', [
+ '-C',
+ remoteDir,
+ '-c',
+ 'user.email=hermes@localhost',
+ '-c',
+ 'user.name=Hermes',
+ 'commit',
+ '--allow-empty',
+ '-m',
+ 'root'
+ ])
// Clone so origin/main exists as a remote-tracking ref.
execFileSync('git', ['clone', remoteDir, cloneDir])
- const result = await addWorktree(cloneDir, { base: 'origin/main', branch: 'feature-branch', name: 'feature-branch' }, 'git')
+ const result = await addWorktree(
+ cloneDir,
+ { base: 'origin/main', branch: 'feature-branch', name: 'feature-branch' },
+ 'git'
+ )
assert.equal(result.branch, 'feature-branch')
diff --git a/apps/desktop/electron/git-worktree-ops.ts b/apps/desktop/electron/git-worktree-ops.ts
index 1acd029e1258..fac77cede71a 100644
--- a/apps/desktop/electron/git-worktree-ops.ts
+++ b/apps/desktop/electron/git-worktree-ops.ts
@@ -378,11 +378,21 @@ async function listBaseBranches(repoPath, gitBin) {
try {
const out = await runGit(
gitBin,
- ['for-each-ref', '--format=%(refname:short)\t%(committerdate:iso)', '--sort=-committerdate', 'refs/heads', 'refs/remotes'],
+ [
+ 'for-each-ref',
+ '--format=%(refname:short)\t%(committerdate:iso)',
+ '--sort=-committerdate',
+ 'refs/heads',
+ 'refs/remotes'
+ ],
resolved
)
- const remoteDefault = await gitLine(gitBin, ['symbolic-ref', '--quiet', '--short', 'refs/remotes/origin/HEAD'], resolved)
+ const remoteDefault = await gitLine(
+ gitBin,
+ ['symbolic-ref', '--quiet', '--short', 'refs/remotes/origin/HEAD'],
+ resolved
+ )
const localDefault = await defaultBranch(gitBin, resolved)
return out
diff --git a/apps/desktop/electron/main.ts b/apps/desktop/electron/main.ts
index de2e7c68ac1a..fb8ecd5613e1 100644
--- a/apps/desktop/electron/main.ts
+++ b/apps/desktop/electron/main.ts
@@ -1,4 +1,3 @@
-
import { execFile, execFileSync, spawn } from 'node:child_process'
import crypto from 'node:crypto'
import fs from 'node:fs'
@@ -65,7 +64,6 @@ import {
} from './desktop-uninstall'
import { installEmbedReferer } from './embed-referer'
import { readDirForIpc } from './fs-read-dir'
-import { resolvePickerDefaultPath } from './wsl-path-bridge'
import { probeGatewayWebSocket } from './gateway-ws-probe'
import { scanGitRepos } from './git-repo-scan'
import {
@@ -84,7 +82,14 @@ import {
reviewUnstage
} from './git-review-ops'
import { gitRootForIpc } from './git-root'
-import { addWorktree, listBaseBranches, listBranches, listWorktrees, removeWorktree, switchBranch } from './git-worktree-ops'
+import {
+ addWorktree,
+ listBaseBranches,
+ listBranches,
+ listWorktrees,
+ removeWorktree,
+ switchBranch
+} from './git-worktree-ops'
import {
DATA_URL_READ_MAX_BYTES,
DEFAULT_FETCH_TIMEOUT_MS,
@@ -127,10 +132,16 @@ import {
MIN_WIDTH as WINDOW_MIN_WIDTH
} from './window-state'
import { hiddenWindowsChildOptions } from './windows-child-options'
-import { buildPathExtCandidates, chooseUpdaterArgs, getVenvSitePackagesEntries, resolveVenvHermesCommand } from './windows-hermes-path'
+import {
+ buildPathExtCandidates,
+ chooseUpdaterArgs,
+ getVenvSitePackagesEntries,
+ resolveVenvHermesCommand
+} from './windows-hermes-path'
import { readWindowsUserEnvVar } from './windows-user-env'
import { isPackagedInstallPath as isPackagedInstallPathUnderRoots } from './workspace-cwd'
import { readWslWindowsClipboardImage } from './wsl-clipboard-image'
+import { resolvePickerDefaultPath } from './wsl-path-bridge'
const USER_DATA_OVERRIDE = process.env.HERMES_DESKTOP_USER_DATA_DIR
@@ -5485,6 +5496,7 @@ function openPortalLoginWindow() {
if (settled) {
return
}
+
settled = true
if (pollTimer) {
@@ -5571,6 +5583,7 @@ async function discoverCloudAgents(org?: string) {
const err = new Error(
'You are not signed in to Hermes Cloud. Open Settings → Gateway, choose Hermes Cloud, and sign in.'
) as any
+
err.needsCloudLogin = true
throw err
}
@@ -5939,6 +5952,7 @@ function buildRemoteBlock(remoteUrl, authMode, token, org?: string) {
authMode,
token
}
+
const orgValue = typeof org === 'string' ? org.trim() : ''
if (orgValue) {
@@ -6934,6 +6948,7 @@ async function startHermes() {
function wireCommonWindowHandlers(win, { zoom = true }: { zoom?: boolean } = {}) {
installPreviewShortcut(win)
installDevToolsShortcut(win)
+
if (zoom) {
installZoomShortcuts(win)
// Re-apply persisted zoom on show/restore (Windows drops webContents zoom on
@@ -6941,6 +6956,7 @@ function wireCommonWindowHandlers(win, { zoom = true }: { zoom?: boolean } = {})
installZoomReassertOnWindowEvents(win, () => restorePersistedZoomLevel(win))
win.webContents.once('did-finish-load', () => restorePersistedZoomLevel(win))
}
+
installContextMenu(win)
win.webContents.setWindowOpenHandler(details => {
openExternalUrl(details.url)
@@ -8523,9 +8539,7 @@ ipcMain.handle('hermes:git:branchSwitch', async (_event, repoPath, branch) =>
ipcMain.handle('hermes:git:branchList', async (_event, repoPath) => listBranches(repoPath, resolveGitBinary()))
-ipcMain.handle('hermes:git:baseBranchList', async (_event, repoPath) =>
- listBaseBranches(repoPath, resolveGitBinary())
-)
+ipcMain.handle('hermes:git:baseBranchList', async (_event, repoPath) => listBaseBranches(repoPath, resolveGitBinary()))
// Compact repo status (branch, ahead/behind, change counts + files) for the
// composer coding rail. Returns null on a non-repo / remote backend so the rail
diff --git a/apps/desktop/electron/windows-hermes-path.test.ts b/apps/desktop/electron/windows-hermes-path.test.ts
index 8600c3501858..778042b278ec 100644
--- a/apps/desktop/electron/windows-hermes-path.test.ts
+++ b/apps/desktop/electron/windows-hermes-path.test.ts
@@ -17,7 +17,12 @@ import path from 'node:path'
import { test } from 'vitest'
-import { buildPathExtCandidates, chooseUpdaterArgs, getVenvSitePackagesEntries, resolveVenvHermesCommand } from './windows-hermes-path'
+import {
+ buildPathExtCandidates,
+ chooseUpdaterArgs,
+ getVenvSitePackagesEntries,
+ resolveVenvHermesCommand
+} from './windows-hermes-path'
test('buildPathExtCandidates: Windows tries PATHEXT extensions before the empty extension', () => {
const extensions = buildPathExtCandidates('.COM;.EXE;.BAT;.CMD', true)
diff --git a/apps/desktop/electron/windows-hermes-path.ts b/apps/desktop/electron/windows-hermes-path.ts
index a40524910ecb..6f3542853e74 100644
--- a/apps/desktop/electron/windows-hermes-path.ts
+++ b/apps/desktop/electron/windows-hermes-path.ts
@@ -111,21 +111,25 @@ export function getVenvSitePackagesEntries(
const isWindows = opts.isWindows ?? process.platform === 'win32'
- const directoryExists = opts.directoryExists ?? ((p: string) => {
- try {
- return fs.statSync(p).isDirectory()
- } catch {
- return false
- }
- })
+ const directoryExists =
+ opts.directoryExists ??
+ ((p: string) => {
+ try {
+ return fs.statSync(p).isDirectory()
+ } catch {
+ return false
+ }
+ })
- const readFile = opts.readFile ?? ((p: string) => {
- try {
- return fs.readFileSync(p, 'utf8')
- } catch {
- return undefined
- }
- })
+ const readFile =
+ opts.readFile ??
+ ((p: string) => {
+ try {
+ return fs.readFileSync(p, 'utf8')
+ } catch {
+ return undefined
+ }
+ })
if (isWindows) {
const sitePackages = path.join(venvRoot, 'Lib', 'site-packages')
diff --git a/apps/desktop/electron/wsl-path-bridge.test.ts b/apps/desktop/electron/wsl-path-bridge.test.ts
index 79505a1d394e..3dcf18e7b39c 100644
--- a/apps/desktop/electron/wsl-path-bridge.test.ts
+++ b/apps/desktop/electron/wsl-path-bridge.test.ts
@@ -1,4 +1,5 @@
import assert from 'node:assert/strict'
+
import { test } from 'vitest'
import { parseDefaultDistro, resolvePickerDefaultPath, wslPosixToWindowsAccessible } from './wsl-path-bridge'
diff --git a/apps/desktop/electron/wsl-path-bridge.ts b/apps/desktop/electron/wsl-path-bridge.ts
index 0b14604df671..60d3c0cf1835 100644
--- a/apps/desktop/electron/wsl-path-bridge.ts
+++ b/apps/desktop/electron/wsl-path-bridge.ts
@@ -53,6 +53,7 @@ export function resolveDefaultWslDistro(): string {
timeout: 2000,
windowsHide: true
})
+
cachedDistro = parseDefaultDistro(out) || 'Ubuntu'
} catch {
cachedDistro = 'Ubuntu'
diff --git a/apps/desktop/src/app/chat/hooks/use-composer-actions.ts b/apps/desktop/src/app/chat/hooks/use-composer-actions.ts
index 2f9a924a0a92..3c31f4067c7f 100644
--- a/apps/desktop/src/app/chat/hooks/use-composer-actions.ts
+++ b/apps/desktop/src/app/chat/hooks/use-composer-actions.ts
@@ -312,21 +312,24 @@ export function useComposerActions({
requestComposerInsert(refText, { mode: 'inline' })
}, [])
- const addContextRefAttachment = useCallback((refText: string, label?: string, detail?: string) => {
- const kind: ComposerAttachment['kind'] = refText.startsWith('@folder:')
- ? 'folder'
- : refText.startsWith('@url:')
- ? 'url'
- : 'file'
+ const addContextRefAttachment = useCallback(
+ (refText: string, label?: string, detail?: string) => {
+ const kind: ComposerAttachment['kind'] = refText.startsWith('@folder:')
+ ? 'folder'
+ : refText.startsWith('@url:')
+ ? 'url'
+ : 'file'
- attachToMain({
- id: attachmentId(kind, refText),
- kind,
- label: label || refText.replace(/^@(file|folder|url):/, ''),
- detail,
- refText
- })
- }, [attachToMain])
+ attachToMain({
+ id: attachmentId(kind, refText),
+ kind,
+ label: label || refText.replace(/^@(file|folder|url):/, ''),
+ detail,
+ refText
+ })
+ },
+ [attachToMain]
+ )
const pickContextPaths = useCallback(
async (kind: 'file' | 'folder') => {
diff --git a/apps/desktop/src/app/chat/session-tile-actions.ts b/apps/desktop/src/app/chat/session-tile-actions.ts
index f75ff6bf4dac..00146a7049b7 100644
--- a/apps/desktop/src/app/chat/session-tile-actions.ts
+++ b/apps/desktop/src/app/chat/session-tile-actions.ts
@@ -176,7 +176,11 @@ export function useSessionTileActions({ runtimeId, scope, storedSessionId }: Ses
...state,
messages: [
...state.messages,
- { id: `system-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`, role: 'system', parts: [textPart(text)] }
+ {
+ id: `system-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
+ role: 'system',
+ parts: [textPart(text)]
+ }
]
}))
},
@@ -354,6 +358,15 @@ export function useSessionTileActions({ runtimeId, scope, storedSessionId }: Ses
steerPrompt,
submitText
}),
- [cancelRun, dismissError, editMessage, handleThreadMessagesChange, reloadFromMessage, restoreToMessage, steerPrompt, submitText]
+ [
+ cancelRun,
+ dismissError,
+ editMessage,
+ handleThreadMessagesChange,
+ reloadFromMessage,
+ restoreToMessage,
+ steerPrompt,
+ submitText
+ ]
)
}
diff --git a/apps/desktop/src/app/chat/sidebar/session-actions-menu.tsx b/apps/desktop/src/app/chat/sidebar/session-actions-menu.tsx
index 8faee82b25e7..d60f10016fc4 100644
--- a/apps/desktop/src/app/chat/sidebar/session-actions-menu.tsx
+++ b/apps/desktop/src/app/chat/sidebar/session-actions-menu.tsx
@@ -2,7 +2,12 @@ import { useStore } from '@nanostores/react'
import type * as React from 'react'
import { useEffect, useRef, useState } from 'react'
-import { closeAllTreeTabs, closeOtherTreeTabs, closeTreeTabsToRight, treeTabCloseTargets } from '@/components/pane-shell/tree/store'
+import {
+ closeAllTreeTabs,
+ closeOtherTreeTabs,
+ closeTreeTabsToRight,
+ treeTabCloseTargets
+} from '@/components/pane-shell/tree/store'
import { Button } from '@/components/ui/button'
import { Codicon } from '@/components/ui/codicon'
import {
diff --git a/apps/desktop/src/app/command-palette/index.tsx b/apps/desktop/src/app/command-palette/index.tsx
index 09d6dac8a2a1..05a4d804cb8c 100644
--- a/apps/desktop/src/app/command-palette/index.tsx
+++ b/apps/desktop/src/app/command-palette/index.tsx
@@ -686,7 +686,12 @@ export function CommandPalette() {
items: sessions.map(session => ({
icon: MessageCircle,
id: `session-${session.id}`,
- keywords: ['chat', 'session', ...(session.preview ? [session.preview] : []), ...(session.git_branch ? [session.git_branch] : [])],
+ keywords: [
+ 'chat',
+ 'session',
+ ...(session.preview ? [session.preview] : []),
+ ...(session.git_branch ? [session.git_branch] : [])
+ ],
label: session.title,
run: go(sessionRoute(session.id))
}))
@@ -724,7 +729,13 @@ export function CommandPalette() {
items: archivedSessions.map(session => ({
icon: Archive,
id: `archived-${session.id}`,
- keywords: ['archived', 'chat', 'session', ...(session.preview ? [session.preview] : []), ...(session.git_branch ? [session.git_branch] : [])],
+ keywords: [
+ 'archived',
+ 'chat',
+ 'session',
+ ...(session.preview ? [session.preview] : []),
+ ...(session.git_branch ? [session.git_branch] : [])
+ ],
label: session.title,
run: go(`${SETTINGS_ROUTE}?tab=sessions&session=${encodeURIComponent(session.id)}`)
}))
diff --git a/apps/desktop/src/app/contrib/hooks/use-background-sync.ts b/apps/desktop/src/app/contrib/hooks/use-background-sync.ts
index bef0647a17d5..2ef5d26cf098 100644
--- a/apps/desktop/src/app/contrib/hooks/use-background-sync.ts
+++ b/apps/desktop/src/app/contrib/hooks/use-background-sync.ts
@@ -116,7 +116,10 @@ export function useBackgroundSync({
return
}
- const dispose = visiblePoll(ACTIVE_MESSAGING_SESSION_POLL_INTERVAL_MS, () => void refreshActiveMessagingTranscript())
+ const dispose = visiblePoll(
+ ACTIVE_MESSAGING_SESSION_POLL_INTERVAL_MS,
+ () => void refreshActiveMessagingTranscript()
+ )
void refreshActiveMessagingTranscript()
return dispose
diff --git a/apps/desktop/src/app/contrib/hooks/use-desktop-integrations.ts b/apps/desktop/src/app/contrib/hooks/use-desktop-integrations.ts
index 6dd78dfb7f8d..d0af49665906 100644
--- a/apps/desktop/src/app/contrib/hooks/use-desktop-integrations.ts
+++ b/apps/desktop/src/app/contrib/hooks/use-desktop-integrations.ts
@@ -3,12 +3,7 @@ import { useEffect, useRef } from 'react'
import { closeActiveTab } from '@/app/chat/close-tab'
import { storedSessionIdForNotification } from '@/lib/session-ids'
import { respondToApprovalAction } from '@/store/native-notifications'
-import {
- getRememberedRoute,
- getRememberedSessionId,
- setRememberedRoute,
- setRememberedSessionId
-} from '@/store/session'
+import { getRememberedRoute, getRememberedSessionId, setRememberedRoute, setRememberedSessionId } from '@/store/session'
import { onSessionsChanged } from '@/store/session-sync'
import { openUpdatesWindow, startUpdatePoller, stopUpdatePoller } from '@/store/updates'
import { isSecondaryWindow } from '@/store/windows'
diff --git a/apps/desktop/src/app/contrib/hooks/use-pet-bridge.ts b/apps/desktop/src/app/contrib/hooks/use-pet-bridge.ts
index b4e0afc0d5e3..2d8a17eb52ba 100644
--- a/apps/desktop/src/app/contrib/hooks/use-pet-bridge.ts
+++ b/apps/desktop/src/app/contrib/hooks/use-pet-bridge.ts
@@ -2,11 +2,7 @@ import { useEffect, useRef } from 'react'
import { setPetActivity } from '@/store/pet'
import { setPetScale } from '@/store/pet-gallery'
-import {
- setPetOverlayOpenAppHandler,
- setPetOverlayScaleHandler,
- setPetOverlaySubmitHandler
-} from '@/store/pet-overlay'
+import { setPetOverlayOpenAppHandler, setPetOverlayScaleHandler, setPetOverlaySubmitHandler } from '@/store/pet-overlay'
import { $attentionSessionIds, $sessions } from '@/store/session'
import { isSecondaryWindow } from '@/store/windows'
diff --git a/apps/desktop/src/app/contrib/panes.tsx b/apps/desktop/src/app/contrib/panes.tsx
index 37aaba24c9ad..484b1025eddd 100644
--- a/apps/desktop/src/app/contrib/panes.tsx
+++ b/apps/desktop/src/app/contrib/panes.tsx
@@ -96,7 +96,10 @@ export function PreviewRailPane() {
className={cn(ZONE_CONTENT, 'min-h-0 w-full overflow-hidden [&>aside]:pt-0')}
style={{ '--titlebar-height': `${TITLEBAR_HEIGHT}px` } as CSSProperties}
>
-
+
)
}
@@ -159,7 +162,11 @@ export function useStatusbarContributions(side: 'left' | 'right'): StatusbarItem
c.render
? ({
id: c.id,
- render: () =>
{c.render!()}
+ render: () => (
+
+ {c.render!()}
+
+ )
} satisfies StatusbarItem)
: (c.data as StatusbarItem)
)
diff --git a/apps/desktop/src/app/cron/cron-job-model.ts b/apps/desktop/src/app/cron/cron-job-model.ts
index 38d3be879f70..0caf6bf2b8b5 100644
--- a/apps/desktop/src/app/cron/cron-job-model.ts
+++ b/apps/desktop/src/app/cron/cron-job-model.ts
@@ -42,10 +42,7 @@ export interface CronEditorSaveValues {
}
/** Build the API update payload, preserving an empty prompt on script-only jobs. */
-export function cronEditorUpdates(
- values: CronEditorSaveValues,
- options: { scriptOnlyJob: boolean }
-): CronJobUpdates {
+export function cronEditorUpdates(values: CronEditorSaveValues, options: { scriptOnlyJob: boolean }): CronJobUpdates {
const updates: CronJobUpdates = {
deliver: values.deliver,
name: values.name,
diff --git a/apps/desktop/src/app/cron/index.tsx b/apps/desktop/src/app/cron/index.tsx
index 52869d0f15e4..0defd4e62a7d 100644
--- a/apps/desktop/src/app/cron/index.tsx
+++ b/apps/desktop/src/app/cron/index.tsx
@@ -399,10 +399,7 @@ export function CronView({ onClose, onOpenSession, setStatusbarItemGroup: _setSt
} else if (editor.mode === 'edit') {
const scriptOnlyJob = jobIsScriptOnly(editor.job)
- const updated = await updateCronJob(
- editor.job.id,
- cronEditorUpdates(values, { scriptOnlyJob })
- )
+ const updated = await updateCronJob(editor.job.id, cronEditorUpdates(values, { scriptOnlyJob }))
updateCronJobs(rows => rows.map(row => (row.id === updated.id ? updated : row)))
notify({ kind: 'success', title: c.updated, message: truncate(jobTitle(updated), 60) })
@@ -818,12 +815,7 @@ function CronEditorDialog({
/>
-
+