mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-20 15:33:54 +00:00
* fix(tui): don't make Enter swallow trailing-space-only slash completions
Submitting a slash command in the TUI took three Enter presses: one to
complete the name (/ex → /exit), a second that only appended the trailing
space the gateway adds to keep the classic-CLI prompt_toolkit dropdown open
(/exit → "/exit "), and a third to actually submit.
The composer's submit handler accepted the highlighted completion whenever
applying it changed the input at all, so the whitespace-only delta ate an
extra keypress. Treat a completion whose only change is trailing whitespace
on an already-complete token as "already complete" and fall through to
submit. Partial-name and argument completions (a real token change) still
accept on Enter as before.
The replace/accept logic is extracted into pure helpers (applyCompletion,
completionToApplyOnSubmit) in domain/slash.ts.
* test(tui): cover Enter/completion trailing-space behavior and isolate poller queue
- completionApply.test.ts asserts completionToApplyOnSubmit accepts real
token completions (partial command name, argument) but returns null for a
trailing-space-only delta on an already-complete command, so Enter submits
instead of needing extra presses.
- test_notification_poller_delivers_completion / _skips_consumed previously
shared the process-global process_registry.completion_queue. Their events
carry no session_key, so a leaked/concurrent poller could dequeue and
dispatch them to a fixture agent without run_conversation, flaking CI
("AttributeError: '_FakeAgent' object has no attribute 'run_conversation'").
Isolate the queue per test (fresh queue.Queue via monkeypatch), matching the
sibling poller tests that already do this.
50 lines
2 KiB
TypeScript
50 lines
2 KiB
TypeScript
/** Appended to `/model` args from the TUI picker for session scope; stripped in `session` slash before `config.set`. */
|
|
export const TUI_SESSION_MODEL_FLAG = '--tui-session'
|
|
|
|
export const looksLikeSlashCommand = (text: string) => /^\/[^\s/]*(?:\s|$)/.test(text)
|
|
|
|
export const parseSlashCommand = (cmd: string) => {
|
|
const [name = '', ...rest] = cmd.slice(1).split(/\s+/)
|
|
|
|
return { arg: rest.join(' '), cmd, name: name.toLowerCase() }
|
|
}
|
|
|
|
/**
|
|
* Apply a completion row to the current input, mirroring the editor's
|
|
* replace semantics: replace from `compReplace` with the row text, dropping
|
|
* the leading slash when both the input and the row carry one (the gateway's
|
|
* slash completer returns bare command names whose replace span begins after
|
|
* the leading `/`).
|
|
*/
|
|
export const applyCompletion = (value: string, rowText: string, compReplace: number): string => {
|
|
const text = value.startsWith('/') && rowText.startsWith('/') ? rowText.slice(1) : rowText
|
|
|
|
return value.slice(0, compReplace) + text
|
|
}
|
|
|
|
/**
|
|
* Decide what Enter does when a completion is highlighted: returns the value
|
|
* to set (accept the completion) or `null` to fall through to submit.
|
|
*
|
|
* Enter accepts a completion only when it changes the command/argument token.
|
|
* A completion that merely appends trailing whitespace to an already-complete
|
|
* command (e.g. `/exit` → `/exit `, the trailing space the gateway adds so the
|
|
* classic CLI's prompt_toolkit dropdown stays open) must NOT swallow the Enter
|
|
* — otherwise every slash command needs an extra keypress: type → Enter
|
|
* completes the name → Enter adds the space → Enter finally submits. Treating a
|
|
* whitespace-only delta as "already complete" collapses that back to the
|
|
* expected one/two presses.
|
|
*/
|
|
export const completionToApplyOnSubmit = (
|
|
value: string,
|
|
rowText: string | undefined,
|
|
compReplace: number
|
|
): string | null => {
|
|
if (!rowText) {
|
|
return null
|
|
}
|
|
|
|
const next = applyCompletion(value, rowText, compReplace)
|
|
|
|
return next !== value && next.trimEnd() !== value.trimEnd() ? next : null
|
|
}
|