mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-04-30 01:41:43 +00:00
feat(tui): replace /clear double-press gate with a proper confirm overlay
The time-window gate felt wrong — users would hit /clear, read the prompt, retype, and consistently blow past the window. Swapping to a real yes/no overlay that blocks input like the existing Approval and Clarify prompts. - add ConfirmReq type + OverlayState.confirm + $isBlocked coverage - ConfirmPrompt component (prompts.tsx): cancel row on top as the default, danger-coloured confirm row on the bottom, Y/N hotkeys, Enter on default = cancel, Esc/Ctrl+C cancel - wire into PromptZone (appOverlays.tsx) - /clear + /new now push onto the overlay instead of arming a timer - HERMES_TUI_NO_CONFIRM=1 still skips the prompt for scripting - drop the destructiveGate + createSlashHandler reset wiring (destructive.ts and its tests removed) Refs #4069.
This commit is contained in:
parent
75377feb07
commit
df5ca5065f
9 changed files with 132 additions and 115 deletions
|
|
@ -9,7 +9,7 @@ import { $uiState } from '../app/uiStore.js'
|
|||
import { FloatBox } from './appChrome.js'
|
||||
import { MaskedPrompt } from './maskedPrompt.js'
|
||||
import { ModelPicker } from './modelPicker.js'
|
||||
import { ApprovalPrompt, ClarifyPrompt } from './prompts.js'
|
||||
import { ApprovalPrompt, ClarifyPrompt, ConfirmPrompt } from './prompts.js'
|
||||
import { SessionPicker } from './sessionPicker.js'
|
||||
import { SkillsHub } from './skillsHub.js'
|
||||
|
||||
|
|
@ -31,6 +31,23 @@ export function PromptZone({
|
|||
)
|
||||
}
|
||||
|
||||
if (overlay.confirm) {
|
||||
const req = overlay.confirm
|
||||
|
||||
const onConfirm = () => {
|
||||
patchOverlayState({ confirm: null })
|
||||
req.onConfirm()
|
||||
}
|
||||
|
||||
const onCancel = () => patchOverlayState({ confirm: null })
|
||||
|
||||
return (
|
||||
<Box flexDirection="column" flexShrink={0} paddingX={1} paddingY={1}>
|
||||
<ConfirmPrompt onCancel={onCancel} onConfirm={onConfirm} req={req} t={ui.theme} />
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
if (overlay.clarify) {
|
||||
return (
|
||||
<Box flexDirection="column" flexShrink={0} paddingX={1} paddingY={1}>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { Box, Text, useInput } from '@hermes/ink'
|
|||
import { useState } from 'react'
|
||||
|
||||
import type { Theme } from '../theme.js'
|
||||
import type { ApprovalReq, ClarifyReq } from '../types.js'
|
||||
import type { ApprovalReq, ClarifyReq, ConfirmReq } from '../types.js'
|
||||
|
||||
import { TextInput } from './textInput.js'
|
||||
|
||||
|
|
@ -151,6 +151,80 @@ export function ClarifyPrompt({ cols = 80, onAnswer, onCancel, req, t }: Clarify
|
|||
)
|
||||
}
|
||||
|
||||
export function ConfirmPrompt({ onCancel, onConfirm, req, t }: ConfirmPromptProps) {
|
||||
const [sel, setSel] = useState(0)
|
||||
|
||||
useInput((ch, key) => {
|
||||
if (key.escape || (key.ctrl && ch.toLowerCase() === 'c')) {
|
||||
onCancel()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
const lower = ch.toLowerCase()
|
||||
|
||||
if (lower === 'y') {
|
||||
onConfirm()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if (lower === 'n') {
|
||||
onCancel()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if (key.upArrow && sel > 0) {
|
||||
setSel(0)
|
||||
}
|
||||
|
||||
if (key.downArrow && sel < 1) {
|
||||
setSel(1)
|
||||
}
|
||||
|
||||
if (key.return) {
|
||||
sel === 0 ? onCancel() : onConfirm()
|
||||
}
|
||||
})
|
||||
|
||||
const accent = req.danger ? t.color.error : t.color.warn
|
||||
const confirmLabel = req.confirmLabel ?? 'Yes'
|
||||
const cancelLabel = req.cancelLabel ?? 'No'
|
||||
|
||||
const rows = [
|
||||
{ color: t.color.cornsilk, label: cancelLabel },
|
||||
{ color: req.danger ? t.color.error : t.color.cornsilk, label: confirmLabel }
|
||||
]
|
||||
|
||||
return (
|
||||
<Box borderColor={accent} borderStyle="double" flexDirection="column" paddingX={1}>
|
||||
<Text bold color={accent}>
|
||||
{req.danger ? '⚠' : '?'} {req.title}
|
||||
</Text>
|
||||
|
||||
{req.detail ? (
|
||||
<Box paddingLeft={1}>
|
||||
<Text color={t.color.cornsilk} wrap="truncate-end">
|
||||
{req.detail}
|
||||
</Text>
|
||||
</Box>
|
||||
) : null}
|
||||
|
||||
<Text />
|
||||
|
||||
{rows.map((row, i) => (
|
||||
<Text key={row.label}>
|
||||
<Text color={sel === i ? accent : t.color.dim}>{sel === i ? '▸ ' : ' '}</Text>
|
||||
<Text color={sel === i ? row.color : t.color.dim}>{row.label}</Text>
|
||||
</Text>
|
||||
))}
|
||||
|
||||
<Text color={t.color.dim}>↑/↓ select · Enter confirm · Y/N quick · Esc cancel</Text>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
interface ApprovalPromptProps {
|
||||
onChoice: (s: string) => void
|
||||
req: ApprovalReq
|
||||
|
|
@ -164,3 +238,10 @@ interface ClarifyPromptProps {
|
|||
req: ClarifyReq
|
||||
t: Theme
|
||||
}
|
||||
|
||||
interface ConfirmPromptProps {
|
||||
onCancel: () => void
|
||||
onConfirm: () => void
|
||||
req: ConfirmReq
|
||||
t: Theme
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue