mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-06-14 09:11:54 +00:00
chore: fmt
This commit is contained in:
parent
00e1d42b9e
commit
17f13013eb
2 changed files with 206 additions and 74 deletions
|
|
@ -13,7 +13,7 @@ import { MessageLine } from './components/messageLine.js'
|
|||
import { ApprovalPrompt, ClarifyPrompt } from './components/prompts.js'
|
||||
import { QueuedMessages } from './components/queuedMessages.js'
|
||||
import { SessionPicker } from './components/sessionPicker.js'
|
||||
import { TextInput } from './components/textInput.js'
|
||||
import { type PasteEvent, TextInput } from './components/textInput.js'
|
||||
import { Thinking } from './components/thinking.js'
|
||||
import { HOTKEYS, INTERPOLATION_RE, PLACEHOLDERS, TOOL_VERBS, ZERO } from './constants.js'
|
||||
import { type GatewayClient, type GatewayEvent } from './gatewayClient.js'
|
||||
|
|
@ -229,18 +229,24 @@ export function App({ gw }: { gw: GatewayClient }) {
|
|||
const [cols, setCols] = useState(stdout?.columns ?? 80)
|
||||
|
||||
useEffect(() => {
|
||||
if (!stdout) {return}
|
||||
if (!stdout) {
|
||||
return
|
||||
}
|
||||
|
||||
const sync = () => setCols(stdout.columns ?? 80)
|
||||
stdout.on('resize', sync)
|
||||
|
||||
// Enable bracketed paste so image-only clipboard paste reaches the app
|
||||
if (stdout.isTTY) {stdout.write('\x1b[?2004h')}
|
||||
if (stdout.isTTY) {
|
||||
stdout.write('\x1b[?2004h')
|
||||
}
|
||||
|
||||
return () => {
|
||||
stdout.off('resize', sync)
|
||||
|
||||
if (stdout.isTTY) {stdout.write('\x1b[?2004l')}
|
||||
if (stdout.isTTY) {
|
||||
stdout.write('\x1b[?2004l')
|
||||
}
|
||||
}
|
||||
}, [stdout])
|
||||
|
||||
|
|
@ -513,14 +519,20 @@ export function App({ gw }: { gw: GatewayClient }) {
|
|||
)
|
||||
|
||||
const handleTextPaste = useCallback(
|
||||
({ bracketed, cursor, hotkey, text, value }: import('./components/textInput.js').PasteEvent) => {
|
||||
if (hotkey) { void paste(false);
|
||||
({ bracketed, cursor, hotkey, text, value }: PasteEvent) => {
|
||||
if (hotkey) {
|
||||
void paste(false)
|
||||
|
||||
return null }
|
||||
return null
|
||||
}
|
||||
|
||||
if (bracketed) {void paste(true)}
|
||||
if (bracketed) {
|
||||
void paste(true)
|
||||
}
|
||||
|
||||
if (!text) {return null}
|
||||
if (!text) {
|
||||
return null
|
||||
}
|
||||
|
||||
const lineCount = text.split('\n').length
|
||||
|
||||
|
|
@ -770,23 +782,38 @@ export function App({ gw }: { gw: GatewayClient }) {
|
|||
|
||||
// ── Input handling ───────────────────────────────────────────────
|
||||
|
||||
const ctrl = (key: { ctrl: boolean }, ch: string, target: string) =>
|
||||
key.ctrl && ch.toLowerCase() === target
|
||||
const ctrl = (key: { ctrl: boolean }, ch: string, target: string) => key.ctrl && ch.toLowerCase() === target
|
||||
|
||||
useInput((ch, key) => {
|
||||
if (isBlocked) {
|
||||
if (pasteReview) {
|
||||
if (key.return) { setPasteReview(null); dispatchSubmission(pasteReview.text, true) }
|
||||
else if (key.escape || ctrl(key, ch, 'c')) { setPasteReview(null); setStatus('ready') }
|
||||
if (key.return) {
|
||||
setPasteReview(null)
|
||||
dispatchSubmission(pasteReview.text, true)
|
||||
} else if (key.escape || ctrl(key, ch, 'c')) {
|
||||
setPasteReview(null)
|
||||
setStatus('ready')
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if (ctrl(key, ch, 'c')) {
|
||||
if (approval) { gw.request('approval.respond', { choice: 'deny', session_id: sid }).catch(() => {}); setApproval(null); sys('denied') }
|
||||
else if (sudo) { gw.request('sudo.respond', { request_id: sudo.requestId, password: '' }).catch(() => {}); setSudo(null); sys('sudo cancelled') }
|
||||
else if (secret) { gw.request('secret.respond', { request_id: secret.requestId, value: '' }).catch(() => {}); setSecret(null); sys('secret entry cancelled') }
|
||||
else if (picker) {setPicker(false)}
|
||||
if (approval) {
|
||||
gw.request('approval.respond', { choice: 'deny', session_id: sid }).catch(() => {})
|
||||
setApproval(null)
|
||||
sys('denied')
|
||||
} else if (sudo) {
|
||||
gw.request('sudo.respond', { request_id: sudo.requestId, password: '' }).catch(() => {})
|
||||
setSudo(null)
|
||||
sys('sudo cancelled')
|
||||
} else if (secret) {
|
||||
gw.request('secret.respond', { request_id: secret.requestId, value: '' }).catch(() => {})
|
||||
setSecret(null)
|
||||
sys('secret entry cancelled')
|
||||
} else if (picker) {
|
||||
setPicker(false)
|
||||
}
|
||||
} else if (key.escape && picker) {
|
||||
setPicker(false)
|
||||
}
|
||||
|
|
@ -803,7 +830,9 @@ export function App({ gw }: { gw: GatewayClient }) {
|
|||
if (!inputBuf.length && key.tab && completions.length) {
|
||||
const row = completions[compIdx]
|
||||
|
||||
if (row) {setInput(input.slice(0, compReplace) + row.text)}
|
||||
if (row) {
|
||||
setInput(input.slice(0, compReplace) + row.text)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
|
@ -811,12 +840,19 @@ export function App({ gw }: { gw: GatewayClient }) {
|
|||
if (key.upArrow && !inputBuf.length) {
|
||||
if (queueRef.current.length) {
|
||||
const idx = queueEditIdx === null ? 0 : (queueEditIdx + 1) % queueRef.current.length
|
||||
setQueueEdit(idx); setHistoryIdx(null); setInput(queueRef.current[idx] ?? '')
|
||||
setQueueEdit(idx)
|
||||
setHistoryIdx(null)
|
||||
setInput(queueRef.current[idx] ?? '')
|
||||
} else if (historyRef.current.length) {
|
||||
const idx = historyIdx === null ? historyRef.current.length - 1 : Math.max(0, historyIdx - 1)
|
||||
|
||||
if (historyIdx === null) {historyDraftRef.current = input}
|
||||
setHistoryIdx(idx); setQueueEdit(null); setInput(historyRef.current[idx] ?? '')
|
||||
if (historyIdx === null) {
|
||||
historyDraftRef.current = input
|
||||
}
|
||||
|
||||
setHistoryIdx(idx)
|
||||
setQueueEdit(null)
|
||||
setInput(historyRef.current[idx] ?? '')
|
||||
}
|
||||
|
||||
return
|
||||
|
|
@ -824,16 +860,24 @@ export function App({ gw }: { gw: GatewayClient }) {
|
|||
|
||||
if (key.downArrow && !inputBuf.length) {
|
||||
if (queueRef.current.length) {
|
||||
const idx = queueEditIdx === null
|
||||
? queueRef.current.length - 1
|
||||
: (queueEditIdx - 1 + queueRef.current.length) % queueRef.current.length
|
||||
const idx =
|
||||
queueEditIdx === null
|
||||
? queueRef.current.length - 1
|
||||
: (queueEditIdx - 1 + queueRef.current.length) % queueRef.current.length
|
||||
|
||||
setQueueEdit(idx); setHistoryIdx(null); setInput(queueRef.current[idx] ?? '')
|
||||
setQueueEdit(idx)
|
||||
setHistoryIdx(null)
|
||||
setInput(queueRef.current[idx] ?? '')
|
||||
} else if (historyIdx !== null) {
|
||||
const next = historyIdx + 1
|
||||
|
||||
if (next >= historyRef.current.length) { setHistoryIdx(null); setInput(historyDraftRef.current) }
|
||||
else { setHistoryIdx(next); setInput(historyRef.current[next] ?? '') }
|
||||
if (next >= historyRef.current.length) {
|
||||
setHistoryIdx(null)
|
||||
setInput(historyDraftRef.current)
|
||||
} else {
|
||||
setHistoryIdx(next)
|
||||
setInput(historyRef.current[next] ?? '')
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
|
|
@ -846,10 +890,20 @@ export function App({ gw }: { gw: GatewayClient }) {
|
|||
const partial = (streaming || buf.current).trimStart()
|
||||
partial ? appendMessage({ role: 'assistant', text: partial + '\n\n*[interrupted]*' }) : sys('interrupted')
|
||||
|
||||
idle(); setReasoning(''); setActivity([]); turnToolsRef.current = []; setStatus('interrupted')
|
||||
idle()
|
||||
setReasoning('')
|
||||
setActivity([])
|
||||
turnToolsRef.current = []
|
||||
setStatus('interrupted')
|
||||
|
||||
if (statusTimerRef.current) {clearTimeout(statusTimerRef.current)}
|
||||
statusTimerRef.current = setTimeout(() => { statusTimerRef.current = null; setStatus('ready') }, 1500)
|
||||
if (statusTimerRef.current) {
|
||||
clearTimeout(statusTimerRef.current)
|
||||
}
|
||||
|
||||
statusTimerRef.current = setTimeout(() => {
|
||||
statusTimerRef.current = null
|
||||
setStatus('ready')
|
||||
}, 1500)
|
||||
} else if (input || inputBuf.length) {
|
||||
clearIn()
|
||||
} else {
|
||||
|
|
@ -859,13 +913,20 @@ export function App({ gw }: { gw: GatewayClient }) {
|
|||
return
|
||||
}
|
||||
|
||||
if (ctrl(key, ch, 'd')) {return die()}
|
||||
if (ctrl(key, ch, 'd')) {
|
||||
return die()
|
||||
}
|
||||
|
||||
if (ctrl(key, ch, 'l')) { setStatus('forging session…'); newSession();
|
||||
if (ctrl(key, ch, 'l')) {
|
||||
setStatus('forging session…')
|
||||
newSession()
|
||||
|
||||
return }
|
||||
return
|
||||
}
|
||||
|
||||
if (ctrl(key, ch, 'g')) {return openEditor()}
|
||||
if (ctrl(key, ch, 'g')) {
|
||||
return openEditor()
|
||||
}
|
||||
})
|
||||
|
||||
// ── Gateway events ───────────────────────────────────────────────
|
||||
|
|
|
|||
|
|
@ -4,9 +4,13 @@ import { useEffect, useRef, useState } from 'react'
|
|||
function wordLeft(s: string, p: number) {
|
||||
let i = p - 1
|
||||
|
||||
while (i > 0 && /\s/.test(s[i]!)) {i--}
|
||||
while (i > 0 && /\s/.test(s[i]!)) {
|
||||
i--
|
||||
}
|
||||
|
||||
while (i > 0 && !/\s/.test(s[i - 1]!)) {i--}
|
||||
while (i > 0 && !/\s/.test(s[i - 1]!)) {
|
||||
i--
|
||||
}
|
||||
|
||||
return Math.max(0, i)
|
||||
}
|
||||
|
|
@ -14,9 +18,13 @@ function wordLeft(s: string, p: number) {
|
|||
function wordRight(s: string, p: number) {
|
||||
let i = p
|
||||
|
||||
while (i < s.length && !/\s/.test(s[i]!)) {i++}
|
||||
while (i < s.length && !/\s/.test(s[i]!)) {
|
||||
i++
|
||||
}
|
||||
|
||||
while (i < s.length && /\s/.test(s[i]!)) {i++}
|
||||
while (i < s.length && /\s/.test(s[i]!)) {
|
||||
i++
|
||||
}
|
||||
|
||||
return i
|
||||
}
|
||||
|
|
@ -77,7 +85,14 @@ export function TextInput({ value, onChange, onPaste, onSubmit, placeholder = ''
|
|||
}
|
||||
}, [value])
|
||||
|
||||
useEffect(() => () => { if (pasteTimer.current) {clearTimeout(pasteTimer.current)} }, [])
|
||||
useEffect(
|
||||
() => () => {
|
||||
if (pasteTimer.current) {
|
||||
clearTimeout(pasteTimer.current)
|
||||
}
|
||||
},
|
||||
[]
|
||||
)
|
||||
|
||||
// ── Buffer ops (synchronous, ref-based) ─────────────────────────
|
||||
|
||||
|
|
@ -88,7 +103,10 @@ export function TextInput({ value, onChange, onPaste, onSubmit, placeholder = ''
|
|||
if (track && next !== prev) {
|
||||
undoStack.current.push({ cursor: curRef.current, value: prev })
|
||||
|
||||
if (undoStack.current.length > 200) {undoStack.current.shift()}
|
||||
if (undoStack.current.length > 200) {
|
||||
undoStack.current.shift()
|
||||
}
|
||||
|
||||
redoStack.current = []
|
||||
}
|
||||
|
||||
|
|
@ -105,7 +123,10 @@ export function TextInput({ value, onChange, onPaste, onSubmit, placeholder = ''
|
|||
const swap = (from: typeof undoStack, to: typeof redoStack) => {
|
||||
const entry = from.current.pop()
|
||||
|
||||
if (!entry) {return}
|
||||
if (!entry) {
|
||||
return
|
||||
}
|
||||
|
||||
to.current.push({ cursor: curRef.current, value: vRef.current })
|
||||
commit(entry.value, entry.cursor, false)
|
||||
}
|
||||
|
|
@ -113,7 +134,9 @@ export function TextInput({ value, onChange, onPaste, onSubmit, placeholder = ''
|
|||
const emitPaste = (e: PasteEvent) => {
|
||||
const handled = onPasteRef.current?.(e)
|
||||
|
||||
if (handled) {commit(handled.value, handled.cursor)}
|
||||
if (handled) {
|
||||
commit(handled.value, handled.cursor)
|
||||
}
|
||||
|
||||
return !!handled
|
||||
}
|
||||
|
|
@ -124,7 +147,9 @@ export function TextInput({ value, onChange, onPaste, onSubmit, placeholder = ''
|
|||
pasteBuf.current = ''
|
||||
pasteTimer.current = null
|
||||
|
||||
if (!text) {return}
|
||||
if (!text) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!emitPaste({ cursor: at, text, value: vRef.current }) && PRINTABLE.test(text)) {
|
||||
commit(vRef.current.slice(0, at) + text + vRef.current.slice(at), at + text.length)
|
||||
|
|
@ -146,15 +171,20 @@ export function TextInput({ value, onChange, onPaste, onSubmit, placeholder = ''
|
|||
|
||||
// Keys handled by App.useInput
|
||||
if (
|
||||
k.upArrow || k.downArrow ||
|
||||
k.upArrow ||
|
||||
k.downArrow ||
|
||||
(k.ctrl && inp === 'c') ||
|
||||
k.tab || (k.shift && k.tab) ||
|
||||
k.pageUp || k.pageDown ||
|
||||
k.tab ||
|
||||
(k.shift && k.tab) ||
|
||||
k.pageUp ||
|
||||
k.pageDown ||
|
||||
k.escape
|
||||
) {return}
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
if (k.return) {
|
||||
;(k.shift || k.meta)
|
||||
k.shift || k.meta
|
||||
? commit(insert(vRef.current, curRef.current, '\n'), curRef.current + 1)
|
||||
: onSubmitRef.current?.(vRef.current)
|
||||
|
||||
|
|
@ -165,46 +195,85 @@ export function TextInput({ value, onChange, onPaste, onSubmit, placeholder = ''
|
|||
let v = vRef.current
|
||||
const mod = k.ctrl || k.meta
|
||||
|
||||
if (k.ctrl && inp === 'z') {return swap(undoStack, redoStack)}
|
||||
|
||||
if ((k.ctrl && inp === 'y') || (k.meta && k.shift && inp === 'z')) {return swap(redoStack, undoStack)}
|
||||
|
||||
if (k.home || (k.ctrl && inp === 'a')) {c = 0}
|
||||
else if (k.end || (k.ctrl && inp === 'e')) {c = v.length}
|
||||
else if (k.leftArrow) {c = mod ? wordLeft(v, c) : Math.max(0, c - 1)}
|
||||
else if (k.rightArrow) {c = mod ? wordRight(v, c) : Math.min(v.length, c + 1)}
|
||||
else if ((k.backspace || k.delete) && c > 0) {
|
||||
if (mod) { const t = wordLeft(v, c); v = v.slice(0, t) + v.slice(c); c = t }
|
||||
else { v = v.slice(0, c - 1) + v.slice(c); c-- }
|
||||
if (k.ctrl && inp === 'z') {
|
||||
return swap(undoStack, redoStack)
|
||||
}
|
||||
else if (k.ctrl && inp === 'w' && c > 0) { const t = wordLeft(v, c); v = v.slice(0, t) + v.slice(c); c = t }
|
||||
else if (k.ctrl && inp === 'u') { v = v.slice(c); c = 0 }
|
||||
else if (k.ctrl && inp === 'k') {v = v.slice(0, c)}
|
||||
else if (k.meta && inp === 'b') {c = wordLeft(v, c)}
|
||||
else if (k.meta && inp === 'f') {c = wordRight(v, c)}
|
||||
else if (inp.length > 0) {
|
||||
|
||||
if ((k.ctrl && inp === 'y') || (k.meta && k.shift && inp === 'z')) {
|
||||
return swap(redoStack, undoStack)
|
||||
}
|
||||
|
||||
if (k.home || (k.ctrl && inp === 'a')) {
|
||||
c = 0
|
||||
} else if (k.end || (k.ctrl && inp === 'e')) {
|
||||
c = v.length
|
||||
} else if (k.leftArrow) {
|
||||
c = mod ? wordLeft(v, c) : Math.max(0, c - 1)
|
||||
} else if (k.rightArrow) {
|
||||
c = mod ? wordRight(v, c) : Math.min(v.length, c + 1)
|
||||
} else if ((k.backspace || k.delete) && c > 0) {
|
||||
if (mod) {
|
||||
const t = wordLeft(v, c)
|
||||
v = v.slice(0, t) + v.slice(c)
|
||||
c = t
|
||||
} else {
|
||||
v = v.slice(0, c - 1) + v.slice(c)
|
||||
c--
|
||||
}
|
||||
} else if (k.ctrl && inp === 'w' && c > 0) {
|
||||
const t = wordLeft(v, c)
|
||||
v = v.slice(0, t) + v.slice(c)
|
||||
c = t
|
||||
} else if (k.ctrl && inp === 'u') {
|
||||
v = v.slice(c)
|
||||
c = 0
|
||||
} else if (k.ctrl && inp === 'k') {
|
||||
v = v.slice(0, c)
|
||||
} else if (k.meta && inp === 'b') {
|
||||
c = wordLeft(v, c)
|
||||
} else if (k.meta && inp === 'f') {
|
||||
c = wordRight(v, c)
|
||||
} else if (inp.length > 0) {
|
||||
const bracketed = inp.includes('[200~')
|
||||
const raw = inp.replace(BRACKET_PASTE, '').replace(/\r\n/g, '\n').replace(/\r/g, '\n')
|
||||
|
||||
if (bracketed && emitPaste({ bracketed: true, cursor: c, text: raw, value: v })) {return}
|
||||
if (bracketed && emitPaste({ bracketed: true, cursor: c, text: raw, value: v })) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!raw) {return}
|
||||
if (!raw) {
|
||||
return
|
||||
}
|
||||
|
||||
if (raw === '\n') {return commit(insert(v, c, '\n'), c + 1)}
|
||||
if (raw === '\n') {
|
||||
return commit(insert(v, c, '\n'), c + 1)
|
||||
}
|
||||
|
||||
if (raw.length > 1 || raw.includes('\n')) {
|
||||
if (!pasteBuf.current) {pastePos.current = c}
|
||||
if (!pasteBuf.current) {
|
||||
pastePos.current = c
|
||||
}
|
||||
|
||||
pasteBuf.current += raw
|
||||
|
||||
if (pasteTimer.current) {clearTimeout(pasteTimer.current)}
|
||||
if (pasteTimer.current) {
|
||||
clearTimeout(pasteTimer.current)
|
||||
}
|
||||
|
||||
pasteTimer.current = setTimeout(flushPaste, 50)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if (PRINTABLE.test(raw)) { v = v.slice(0, c) + raw + v.slice(c); c += raw.length }
|
||||
else {return}
|
||||
} else {return}
|
||||
if (PRINTABLE.test(raw)) {
|
||||
v = v.slice(0, c) + raw + v.slice(c)
|
||||
c += raw.length
|
||||
} else {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
return
|
||||
}
|
||||
|
||||
commit(v, c)
|
||||
},
|
||||
|
|
@ -213,7 +282,9 @@ export function TextInput({ value, onChange, onPaste, onSubmit, placeholder = ''
|
|||
|
||||
// ── Render ──────────────────────────────────────────────────────
|
||||
|
||||
if (!focus) {return <Text>{value || (placeholder ? DIM + placeholder + DIM_OFF : '')}</Text>}
|
||||
if (!focus) {
|
||||
return <Text>{value || (placeholder ? DIM + placeholder + DIM_OFF : '')}</Text>
|
||||
}
|
||||
|
||||
if (!value && placeholder) {
|
||||
return <Text>{INV + (placeholder[0] ?? ' ') + INV_OFF + DIM + placeholder.slice(1) + DIM_OFF}</Text>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue