mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-08 03:01:47 +00:00
fix(tui): honor client copy shortcut over ssh
- accept forwarded Cmd+C for selection copy in SSH sessions even when Hermes runs on Linux - keep local Linux Alt+C from acting as copy and update TUI hotkey hints for remote shells
This commit is contained in:
parent
283c8fd6e2
commit
bcc5362432
4 changed files with 48 additions and 10 deletions
|
|
@ -31,6 +31,28 @@ describe('platform action modifier', () => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('isCopyShortcut', () => {
|
||||||
|
it('keeps Ctrl+C as the local non-macOS copy chord', async () => {
|
||||||
|
const { isCopyShortcut } = await importPlatform('linux')
|
||||||
|
|
||||||
|
expect(isCopyShortcut({ ctrl: true, meta: false, super: false }, 'c', {})).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('accepts client Cmd+C over SSH even when running on Linux', async () => {
|
||||||
|
const { isCopyShortcut } = await importPlatform('linux')
|
||||||
|
const env = { SSH_CONNECTION: '1 2 3 4' } as NodeJS.ProcessEnv
|
||||||
|
|
||||||
|
expect(isCopyShortcut({ ctrl: false, meta: false, super: true }, 'c', env)).toBe(true)
|
||||||
|
expect(isCopyShortcut({ ctrl: false, meta: true, super: false }, 'c', env)).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('does not treat local Linux Alt+C as copy', async () => {
|
||||||
|
const { isCopyShortcut } = await importPlatform('linux')
|
||||||
|
|
||||||
|
expect(isCopyShortcut({ ctrl: false, meta: true, super: false }, 'c', {})).toBe(false)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe('isVoiceToggleKey', () => {
|
describe('isVoiceToggleKey', () => {
|
||||||
it('matches raw Ctrl+B on macOS (doc-default across platforms)', async () => {
|
it('matches raw Ctrl+B on macOS (doc-default across platforms)', async () => {
|
||||||
const { isVoiceToggleKey } = await importPlatform('darwin')
|
const { isVoiceToggleKey } = await importPlatform('darwin')
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import type {
|
||||||
SudoRespondResponse,
|
SudoRespondResponse,
|
||||||
VoiceRecordResponse
|
VoiceRecordResponse
|
||||||
} from '../gatewayTypes.js'
|
} from '../gatewayTypes.js'
|
||||||
import { isAction, isMac, isVoiceToggleKey } from '../lib/platform.js'
|
import { isAction, isCopyShortcut, isMac, isVoiceToggleKey } from '../lib/platform.js'
|
||||||
|
|
||||||
import { getInputSelection } from './inputSelectionStore.js'
|
import { getInputSelection } from './inputSelectionStore.js'
|
||||||
import type { InputHandlerContext, InputHandlerResult } from './interfaces.js'
|
import type { InputHandlerContext, InputHandlerResult } from './interfaces.js'
|
||||||
|
|
@ -315,7 +315,7 @@ export function useInputHandlers(ctx: InputHandlerContext): InputHandlerResult {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isAction(key, ch, 'c')) {
|
if (isCopyShortcut(key, ch)) {
|
||||||
if (terminal.hasSelection) {
|
if (terminal.hasSelection) {
|
||||||
return copySelection()
|
return copySelection()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,21 @@
|
||||||
import { isMac } from '../lib/platform.js'
|
import { isMac } from '../lib/platform.js'
|
||||||
|
|
||||||
|
const isRemoteShell = Boolean(process.env.SSH_CONNECTION || process.env.SSH_CLIENT || process.env.SSH_TTY)
|
||||||
const action = isMac ? 'Cmd' : 'Ctrl'
|
const action = isMac ? 'Cmd' : 'Ctrl'
|
||||||
const paste = isMac ? 'Cmd' : 'Alt'
|
const paste = isMac ? 'Cmd' : 'Alt'
|
||||||
|
|
||||||
|
const copyHotkeys: [string, string][] = isMac
|
||||||
|
? [
|
||||||
|
['Cmd+C', 'copy selection'],
|
||||||
|
['Ctrl+C', 'interrupt / clear draft / exit']
|
||||||
|
]
|
||||||
|
: [
|
||||||
|
...(isRemoteShell ? ([['Cmd+C', 'copy selection when forwarded by the terminal']] as [string, string][]) : []),
|
||||||
|
['Ctrl+C', 'copy selection / interrupt / clear draft / exit']
|
||||||
|
]
|
||||||
|
|
||||||
export const HOTKEYS: [string, string][] = [
|
export const HOTKEYS: [string, string][] = [
|
||||||
...(isMac
|
...copyHotkeys,
|
||||||
? ([
|
|
||||||
['Cmd+C', 'copy selection'],
|
|
||||||
['Ctrl+C', 'interrupt / clear draft / exit']
|
|
||||||
] as [string, string][])
|
|
||||||
: ([['Ctrl+C', 'copy selection / interrupt / clear draft / exit']] as [string, string][])),
|
|
||||||
[action + '+D', 'exit'],
|
[action + '+D', 'exit'],
|
||||||
[action + '+G', 'open $EDITOR for prompt'],
|
[action + '+G', 'open $EDITOR for prompt'],
|
||||||
[action + '+L', 'new session (clear)'],
|
[action + '+L', 'new session (clear)'],
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,8 @@
|
||||||
* as `key.meta`. Some macOS terminals also translate Cmd+Left/Right/Backspace
|
* as `key.meta`. Some macOS terminals also translate Cmd+Left/Right/Backspace
|
||||||
* into readline-style Ctrl+A/Ctrl+E/Ctrl+U before the app sees them.
|
* into readline-style Ctrl+A/Ctrl+E/Ctrl+U before the app sees them.
|
||||||
* On other platforms the action modifier is Ctrl.
|
* On other platforms the action modifier is Ctrl.
|
||||||
* Ctrl+C is ALWAYS the interrupt key regardless of platform — it must never be
|
* Ctrl+C stays the interrupt key on macOS. On non-mac terminals it can also
|
||||||
* remapped to copy.
|
* copy an active TUI selection, matching common terminal selection behavior.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export const isMac = process.platform === 'darwin'
|
export const isMac = process.platform === 'darwin'
|
||||||
|
|
@ -34,6 +34,16 @@ export const isMacActionFallback = (
|
||||||
export const isAction = (key: { ctrl: boolean; meta: boolean; super?: boolean }, ch: string, target: string): boolean =>
|
export const isAction = (key: { ctrl: boolean; meta: boolean; super?: boolean }, ch: string, target: string): boolean =>
|
||||||
isActionMod(key) && ch.toLowerCase() === target
|
isActionMod(key) && ch.toLowerCase() === target
|
||||||
|
|
||||||
|
const isRemoteShell = (env: NodeJS.ProcessEnv = process.env): boolean =>
|
||||||
|
Boolean(env.SSH_CONNECTION || env.SSH_CLIENT || env.SSH_TTY)
|
||||||
|
|
||||||
|
export const isCopyShortcut = (
|
||||||
|
key: { ctrl: boolean; meta: boolean; super?: boolean },
|
||||||
|
ch: string,
|
||||||
|
env: NodeJS.ProcessEnv = process.env
|
||||||
|
): boolean =>
|
||||||
|
isAction(key, ch, 'c') || (isRemoteShell(env) && (key.meta || key.super === true) && ch.toLowerCase() === 'c')
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Voice recording toggle key (Ctrl+B).
|
* Voice recording toggle key (Ctrl+B).
|
||||||
*
|
*
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue