feat(cli,tui): add display.bell_on_approval + fix eslint error
- display.bell_on_approval (default false): same BEL mechanism as bell_on_complete, rings when a dangerous-command approval prompt opens (_approval_callback / approval.request event). Complements bell_on_clarify from the previous commit. - fix(ui-tui): eslint curly error in useConfigSync.applyDisplay (if without braces) that failed the CI JS & TS checks job.
This commit is contained in:
@@ -1774,6 +1774,12 @@ display:
|
||||
# false: Silent (default)
|
||||
bell_on_clarify: false
|
||||
|
||||
# Play terminal bell when a dangerous-command approval prompt opens.
|
||||
# Same mechanism as bell_on_complete (\\a) — works over SSH.
|
||||
# true: Ring on every approval prompt
|
||||
# false: Silent (default)
|
||||
bell_on_approval: false
|
||||
|
||||
# Show model reasoning/thinking before each response.
|
||||
# When enabled, a dim box shows the model's thought process above the response.
|
||||
# Toggle at runtime with /reasoning show or /reasoning hide.
|
||||
|
||||
@@ -5261,6 +5261,8 @@ class HermesCLI(CLIAgentSetupMixin, CLICommandsMixin, CLIBillingMixin):
|
||||
self.bell_on_complete = CLI_CONFIG["display"].get("bell_on_complete", False)
|
||||
# bell_on_clarify: play terminal bell (\a) when agent asks a clarify question — same mechanism as bell_on_complete
|
||||
self.bell_on_clarify = CLI_CONFIG["display"].get("bell_on_clarify", False)
|
||||
# bell_on_approval: play terminal bell (\a) when a dangerous-command approval prompt opens — same mechanism as bell_on_complete
|
||||
self.bell_on_approval = CLI_CONFIG["display"].get("bell_on_approval", False)
|
||||
# show_reasoning: display model thinking/reasoning before the response
|
||||
self.show_reasoning = CLI_CONFIG["display"].get("show_reasoning", True)
|
||||
# reasoning_full: when reasoning display is on, print the post-response
|
||||
@@ -16537,6 +16539,14 @@ class HermesCLI(CLIAgentSetupMixin, CLICommandsMixin, CLIBillingMixin):
|
||||
}
|
||||
self._approval_deadline = _time.monotonic() + timeout
|
||||
|
||||
# Bell on approval (same mechanism as bell_on_complete — \a over SSH)
|
||||
if getattr(self, "bell_on_approval", False):
|
||||
try:
|
||||
sys.stdout.write("\a")
|
||||
sys.stdout.flush()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Modal prompt — paint immediately, bypassing the throttle/resize
|
||||
# guard. A throttled paint here can be silently dropped (250ms
|
||||
# window collision or in-flight resize), leaving the panel unseen so
|
||||
|
||||
@@ -1476,6 +1476,7 @@ DEFAULT_CONFIG = {
|
||||
"tui_agents_nudge": True,
|
||||
"bell_on_complete": False,
|
||||
"bell_on_clarify": False,
|
||||
"bell_on_approval": False,
|
||||
# Stream the model's reasoning/thinking live before the response.
|
||||
# Default ON: on thinking models the reasoning phase can run tens of
|
||||
# seconds, and with this off the user stares at a spinner the whole
|
||||
|
||||
@@ -420,7 +420,7 @@ export function createGatewayEventHandler(ctx: GatewayEventHandlerContext): (ev:
|
||||
|
||||
const { rpc } = ctx.gateway
|
||||
const { STARTUP_RESUME_ID, newSession, recoverSidRef, resumeById, setCatalog } = ctx.session
|
||||
const { bellOnClarify, bellOnComplete, stdout, sys } = ctx.system
|
||||
const { bellOnApproval, bellOnClarify, bellOnComplete, stdout, sys } = ctx.system
|
||||
const { appendMessage, panel, setHistoryItems } = ctx.transcript
|
||||
const { setInput } = ctx.composer
|
||||
const { submitLiteralRef, submitRef } = ctx.submission
|
||||
@@ -1250,6 +1250,7 @@ export function createGatewayEventHandler(ctx: GatewayEventHandlerContext): (ev:
|
||||
}
|
||||
})
|
||||
setStatus('waiting for input…')
|
||||
|
||||
// Same BEL mechanism as bell_on_complete — works over SSH, triggers tmux bell-action
|
||||
if (bellOnClarify && stdout?.isTTY) {
|
||||
stdout.write('\x07')
|
||||
@@ -1274,6 +1275,11 @@ export function createGatewayEventHandler(ctx: GatewayEventHandlerContext): (ev:
|
||||
})
|
||||
setStatus('approval needed')
|
||||
|
||||
// Same BEL mechanism as bell_on_complete — works over SSH, triggers tmux bell-action
|
||||
if (bellOnApproval && stdout?.isTTY) {
|
||||
stdout.write('\x07')
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -499,6 +499,7 @@ export interface GatewayEventHandlerContext {
|
||||
system: {
|
||||
bellOnComplete: boolean
|
||||
bellOnClarify?: boolean
|
||||
bellOnApproval?: boolean
|
||||
stdout?: NodeJS.WriteStream
|
||||
sys: (text: string) => void
|
||||
}
|
||||
|
||||
@@ -254,10 +254,11 @@ export async function hydrateFullConfig(
|
||||
gw: GatewayClient,
|
||||
setBell: (v: boolean) => void,
|
||||
setVoiceRecordKey?: (v: ParsedVoiceRecordKey) => void,
|
||||
setBellOnClarify?: (v: boolean) => void
|
||||
setBellOnClarify?: (v: boolean) => void,
|
||||
setBellOnApproval?: (v: boolean) => void
|
||||
): Promise<ConfigFullResponse | null> {
|
||||
const cfg = await quietRpc<ConfigFullResponse>(gw, 'config.get', { key: 'full' })
|
||||
applyDisplay(cfg, setBell, setVoiceRecordKey, setBellOnClarify)
|
||||
applyDisplay(cfg, setBell, setVoiceRecordKey, setBellOnClarify, setBellOnApproval)
|
||||
|
||||
return cfg
|
||||
}
|
||||
@@ -266,13 +267,21 @@ export const applyDisplay = (
|
||||
cfg: ConfigFullResponse | null,
|
||||
setBell: (v: boolean) => void,
|
||||
setVoiceRecordKey?: (v: ParsedVoiceRecordKey) => void,
|
||||
setBellOnClarify?: (v: boolean) => void
|
||||
setBellOnClarify?: (v: boolean) => void,
|
||||
setBellOnApproval?: (v: boolean) => void
|
||||
) => {
|
||||
const d = cfg?.config?.display ?? {}
|
||||
const approvals = cfg?.config?.approvals
|
||||
|
||||
setBell(!!d.bell_on_complete)
|
||||
if (setBellOnClarify) setBellOnClarify(!!d.bell_on_clarify)
|
||||
|
||||
if (setBellOnClarify) {
|
||||
setBellOnClarify(!!d.bell_on_clarify)
|
||||
}
|
||||
|
||||
if (setBellOnApproval) {
|
||||
setBellOnApproval(!!d.bell_on_approval)
|
||||
}
|
||||
|
||||
applyConfiguredTuiTheme(d.tui_theme)
|
||||
|
||||
@@ -318,6 +327,7 @@ export function useConfigSync({
|
||||
gw,
|
||||
setBellOnComplete,
|
||||
setBellOnClarify,
|
||||
setBellOnApproval,
|
||||
setVoiceEnabled,
|
||||
setVoiceRecordKey,
|
||||
sid
|
||||
@@ -343,8 +353,8 @@ export function useConfigSync({
|
||||
// mcp_rev) look like an MCP change and fire a needless reload.mcp.
|
||||
mcpRevRef.current.accepted = String(r?.mcp_rev ?? '')
|
||||
})
|
||||
void hydrateFullConfig(gw, setBellOnComplete, setVoiceRecordKey, setBellOnClarify)
|
||||
}, [gw, setBellOnComplete, setBellOnClarify, setVoiceEnabled, setVoiceRecordKey, sid])
|
||||
void hydrateFullConfig(gw, setBellOnComplete, setVoiceRecordKey, setBellOnClarify, setBellOnApproval)
|
||||
}, [gw, setBellOnComplete, setBellOnClarify, setBellOnApproval, setVoiceEnabled, setVoiceRecordKey, sid])
|
||||
|
||||
useEffect(() => {
|
||||
if (!sid) {
|
||||
@@ -391,18 +401,19 @@ export function useConfigSync({
|
||||
)
|
||||
}
|
||||
|
||||
void hydrateFullConfig(gw, setBellOnComplete, setVoiceRecordKey, setBellOnClarify)
|
||||
void hydrateFullConfig(gw, setBellOnComplete, setVoiceRecordKey, setBellOnClarify, setBellOnApproval)
|
||||
})
|
||||
}, MTIME_POLL_MS)
|
||||
|
||||
return () => clearInterval(id)
|
||||
}, [gw, setBellOnComplete, setBellOnClarify, setVoiceRecordKey, sid])
|
||||
}, [gw, setBellOnComplete, setBellOnClarify, setBellOnApproval, setVoiceRecordKey, sid])
|
||||
}
|
||||
|
||||
export interface UseConfigSyncOptions {
|
||||
gw: GatewayClient
|
||||
setBellOnComplete: (v: boolean) => void
|
||||
setBellOnClarify?: (v: boolean) => void
|
||||
setBellOnApproval?: (v: boolean) => void
|
||||
setVoiceEnabled: (v: boolean) => void
|
||||
setVoiceRecordKey?: (v: ParsedVoiceRecordKey) => void
|
||||
sid: null | string
|
||||
|
||||
@@ -208,6 +208,7 @@ export function useMainApp(gw: GatewayClient) {
|
||||
const goodVibesTick = useStore($goodVibesTick)
|
||||
const [bellOnComplete, setBellOnComplete] = useState(false)
|
||||
const [bellOnClarify, setBellOnClarify] = useState(false)
|
||||
const [bellOnApproval, setBellOnApproval] = useState(false)
|
||||
|
||||
const ui = useStore($uiState)
|
||||
const overlay = useStore($overlayState)
|
||||
@@ -579,7 +580,7 @@ export function useMainApp(gw: GatewayClient) {
|
||||
}
|
||||
}, [ui.busy, turnStartedAt])
|
||||
|
||||
useConfigSync({ gw, setBellOnComplete, setBellOnClarify, setVoiceEnabled, setVoiceRecordKey, sid: ui.sid })
|
||||
useConfigSync({ gw, setBellOnComplete, setBellOnClarify, setBellOnApproval, setVoiceEnabled, setVoiceRecordKey, sid: ui.sid })
|
||||
useBatteryPoll(gw)
|
||||
|
||||
useEffect(() => {
|
||||
@@ -858,7 +859,7 @@ export function useMainApp(gw: GatewayClient) {
|
||||
setCatalog
|
||||
},
|
||||
submission: { submitLiteralRef, submitRef },
|
||||
system: { bellOnComplete, bellOnClarify, stdout, sys },
|
||||
system: { bellOnComplete, bellOnClarify, bellOnApproval, stdout, sys },
|
||||
transcript: { appendMessage, panel, setHistoryItems },
|
||||
voice: {
|
||||
setProcessing: setVoiceProcessing,
|
||||
@@ -869,6 +870,7 @@ export function useMainApp(gw: GatewayClient) {
|
||||
}),
|
||||
[
|
||||
appendMessage,
|
||||
bellOnApproval,
|
||||
bellOnClarify,
|
||||
bellOnComplete,
|
||||
composerActions.setInput,
|
||||
|
||||
@@ -80,6 +80,7 @@ export interface ConfigDisplayConfig {
|
||||
battery?: boolean
|
||||
bell_on_complete?: boolean
|
||||
bell_on_clarify?: boolean
|
||||
bell_on_approval?: boolean
|
||||
busy_input_mode?: string
|
||||
details_mode?: string
|
||||
/** Focus view (/focus) — display-only reduced-output mode. */
|
||||
|
||||
@@ -1910,6 +1910,7 @@ display:
|
||||
resume_display: full # full (show previous messages on resume) | minimal (one-liner only)
|
||||
bell_on_complete: false # Play terminal bell when agent finishes (great for long tasks)
|
||||
bell_on_clarify: false # Play terminal bell when the agent asks a clarification question (same BEL mechanism, works over SSH)
|
||||
bell_on_approval: false # Play terminal bell when a dangerous-command approval prompt opens (same BEL mechanism, works over SSH)
|
||||
show_reasoning: true # Show model reasoning/thinking above each response (default: true; toggle with /reasoning show|hide)
|
||||
streaming: false # Stream tokens to terminal as they arrive (real-time output)
|
||||
show_cost: false # Show estimated $ cost in the CLI status bar
|
||||
|
||||
Reference in New Issue
Block a user