fix(tui): guard /update against hosted dashboard mode

/update calls dieWithCode(42) which tears down the gateway and
hard-exits the Node process — the same PTY-killing path that /exit
and /quit use.  In the hosted dashboard chat there is no Python
update wrapper to catch exit code 42, and the PTY death bricks the
tab until a browser refresh.

Mirror the DASHBOARD_TUI_MODE guard that #48882 added for /exit and
/quit: refuse early with an explanatory message.
This commit is contained in:
srojk34 2026-06-19 10:49:38 +03:00 committed by Teknium
parent 9a2f2756f7
commit a7b4fbcbc1
2 changed files with 26 additions and 1 deletions

View file

@ -2,7 +2,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'
import { createSlashHandler } from '../app/createSlashHandler.js'
import { getOverlayState, resetOverlayState } from '../app/overlayStore.js'
import { DASHBOARD_EXIT_DISABLED_MESSAGE } from '../app/slash/commands/core.js'
import { DASHBOARD_EXIT_DISABLED_MESSAGE, DASHBOARD_UPDATE_DISABLED_MESSAGE } from '../app/slash/commands/core.js'
import { getUiState, patchUiState, resetUiState } from '../app/uiStore.js'
import { TUI_SESSION_MODEL_FLAG } from '../domain/slash.js'
@ -118,6 +118,22 @@ describe('createSlashHandler', () => {
vi.useRealTimers()
})
it('refuses /update in hosted dashboard chat instead of killing the PTY', () => {
vi.useFakeTimers()
envState.dashboardTuiMode = true
const ctx = buildCtx()
expect(createSlashHandler(ctx)('/update')).toBe(true)
expect(ctx.session.dieWithCode).not.toHaveBeenCalled()
expect(ctx.gateway.gw.request).not.toHaveBeenCalled()
expect(ctx.transcript.sys).toHaveBeenCalledWith(DASHBOARD_UPDATE_DISABLED_MESSAGE)
vi.advanceTimersByTime(150)
expect(ctx.session.dieWithCode).not.toHaveBeenCalled()
vi.useRealTimers()
})
it('routes /status to live session.status instead of slash worker', async () => {
patchUiState({ sid: 'sid-abc' })
const rpc = vi.fn(() => Promise.resolve({ output: 'Hermes TUI Status' }))

View file

@ -81,6 +81,9 @@ const DETAILS_SECTION_USAGE = 'usage: /details <section> [hidden|collapsed|expan
export const DASHBOARD_EXIT_DISABLED_MESSAGE =
'exit is disabled in hosted dashboard chat — use /new to start a fresh session'
export const DASHBOARD_UPDATE_DISABLED_MESSAGE =
'update is disabled in hosted dashboard chat — the hosted environment is managed separately'
export const coreCommands: SlashCommand[] = [
{
help: 'list commands + hotkeys',
@ -140,6 +143,12 @@ export const coreCommands: SlashCommand[] = [
help: 'update Hermes Agent to the latest version (exits TUI)',
name: 'update',
run: (_arg, ctx) => {
if (DASHBOARD_TUI_MODE) {
ctx.transcript.sys(DASHBOARD_UPDATE_DISABLED_MESSAGE)
return
}
ctx.transcript.sys('exiting TUI to run update...')
// Exit code 42 signals the Python wrapper to exec `hermes update`.
// Use dieWithCode for proper cleanup (gateway kill + Ink unmount).