Three related bugs in the React TUI slash-command flow (fixes#183):
1. Tab completion left a trailing space after the command, so hitting
Enter right after Tab would submit "/exit " (with arg) instead of
running /exit. Drop the space — user can add one themselves if they
want to type an argument.
2. After a programmatic setInput() from completion/history, the
MultilineTextInput cursor stayed at its previous offset instead of
moving to the end of the newly-inserted text. Subsequent keystrokes
landed in the middle of the completed command ("/eXxit" when typing
X after Tab-completing "/exit"). Track the last internally-authored
value via a ref; when the incoming value differs, treat it as an
external change and snap the cursor to the end.
3. /quit is the other obvious word for "exit" and users were surprised
when it wasn't registered. Add an `aliases` field to SlashCommand
and make /quit an alias of /exit. help/list output deduplicates
aliases so they resolve via lookup without cluttering the command
listing.
Tests:
- tests/test_commands/test_registry.py: new tests for the alias path
and for help/list dedup. Both pass locally.
- MultilineTextInput doesn't have a standalone unit test for the
cursor behavior (uses node:test + ink render harness, can't run
without node toolchain on this machine), but the fix is a local,
obvious change in a single component.
Co-authored-by: Yufeng He <40085740+universeplayer@users.noreply.github.com>
Ink reports the common DEL byte as a delete key, which means React TUI prompt editing can lose Backspace behavior on terminals that send 0x7f for backward delete. This change records the raw input sequence so PromptInput can treat raw DEL as backward delete while preserving true forward-delete escape sequences, and it adds a focused regression test plus changelog entry.\n\nConstraint: Ink normalizes raw DEL into key.delete for some terminal environments\nRejected: Treat delete at end-of-line as backspace | breaks true forward delete semantics on terminals that send distinct delete sequences\nConfidence: high\nScope-risk: narrow\nReversibility: clean\nDirective: Keep raw-sequence disambiguation aligned with Ink input semantics before refactoring PromptInput keyboard handling\nTested: /Users/wangxin/.openharness-venv/bin/ruff check src tests scripts\nTested: /Users/wangxin/.openharness-venv/bin/pytest -q\nTested: cd frontend/terminal && npx tsc --noEmit\nTested: cd frontend/terminal && node --import tsx --test src/components/MarkdownText.test.tsx src/components/PromptInput.test.tsx\nNot-tested: Manual verification on non-macOS terminals\nRelated: d4eae15
Co-authored-by: wangxin <wangxinwxwx@shopee.com>
* fix(tui): write newline on exit so shell prompt starts on fresh line
Ink hides the cursor during rendering and restores it on exit, but does
not emit a trailing newline. When the TUI process ends, the terminal
cursor sits at the end of the last rendered line, causing the shell
prompt to appear directly concatenated with the TUI output.
Rename restoreCursor → restoreTerminal and append '\n' alongside the
cursor-show escape sequence so the parent shell always receives the
prompt on a clean new line.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* test(tui): add regression guard for exit newline; update changelog
- Add tests/test_ui/test_tui_exit_sequence.py with two tests:
* test_tui_exit_handler_writes_newline: asserts the cleanup write
includes the trailing \n alongside \x1B[?25h
* test_tui_exit_handler_registered_for_all_signals: asserts cleanup
is registered for 'exit', SIGINT, and SIGTERM
- Add entry to CHANGELOG.md [Unreleased] Fixed section
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix(tests): update ohmo cli test inputs for allow_remote_admin_commands prompt
Commit dd1d235 added a new 'Allow explicitly listed administrative slash
commands from remote channels?' confirmation step to the gateway config
wizard, but the four interactive test cases in test_ohmo/test_cli.py were
not updated to provide an answer for it. This caused stdin to be exhausted
and click to emit Abort(), making all four tests fail with exit_code=1.
Add 'n' as the answer for allow_remote_admin_commands in each affected
test, and move the existing 'restart gateway' answer after it in
test_ohmo_config_interactive_can_restart_gateway.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Remove premature setBusy(false) from assistant_complete handler;
busy state should only clear at true end-of-turn (line_complete).
- Add setBusy(true) to tool_started handler so the spinner activates
even when tool calls come immediately after an assistant message.
- Show 'Processing...' label on tool_completed instead of clearing label.
- Improve inline comments to clarify the intended event flow.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Previously, tool calls and their results were rendered as independent
rows. This flooded the transcript with raw result text on every agent
turn and gave no at-a-glance signal about whether a call succeeded.
Changes:
- ConversationView groups adjacent tool/tool_result items into pairs
before rendering, using a new groupToolPairs() helper.
- ToolCallDisplay accepts an optional resultItem prop and renders a
compound row:
* Success → result line count shown inline (e.g. "→ 24L")
* Error → red error icon inline + up to 5 error lines expanded
- Standalone successful tool_result rows are suppressed (no output).
- Standalone tool_result errors are still surfaced so failures are
never silently swallowed.
Closes#109
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The Ink React TUI crashes with unhandled EIO errors when stdin.setRawMode()
is called during React reconciler mount/unmount cycles. This happens
reliably in SSH, tmux, and Docker terminal environments, causing the
spinner to appear frozen on tool execution.
Changes:
- Monkey-patch stdin.setRawMode to catch EIO/EAGAIN gracefully
- Add uncaughtException handler as fallback for React internals
- Wire up the existing --debug CLI flag to logging.basicConfig
- Support OPENHARNESS_LOG_LEVEL env var for log level control
- Add debug logging to tool execution loop (permission, timing, output)
- Add debug logging to backend event emission
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
On WSL/Windows, the `npm exec -- tsx` process chain can spawn
intermediate shell processes that break TTY stdin inheritance.
When stdin loses its TTY status, Ink's useInput silently fails
to enable raw mode, making the permission dialog unresponsive.
Two fixes:
1. react_launcher.py: Invoke the tsx binary directly from
node_modules/.bin/ instead of going through npm exec, which
preserves TTY inheritance across the process boundary.
2. index.tsx: When process.stdin is not a TTY, open /dev/tty
directly as a fallback stdin stream for Ink, giving raw-mode
keyboard access regardless of how stdin was inherited.
Closes#37
Bug 1 (network error): API errors now yield ErrorEvent instead of silently
hanging the spinner. The query loop catches ConnectionError/TimeoutError
and surfaces a user-visible message.
Bug 2 (bash stuck): Permission modal handler is now explicitly placed
before the busy-guard in useInput, with a question-modal early return.
Added 300s timeout on backend permission future to prevent permanent hang.
Added ErrorEvent to StreamEvent union for TUI error display.
Fixes#36
The theme-frontend agent accidentally removed the ASCII logo when
adding theme support. Restored from initial commit (5dd8b95) with
theme.colors.primary integration.
- Show "Connecting to backend..." instead of stale "model: unknown" during init
- Hide input prompt, status bar, and keyboard hints until backend is ready
- Block message submission while backend is not ready
- Restore terminal cursor visibility on exit (Ink hides it by default)
- Replace oversized ASCII art banner with compact 2-line header
- Kill entire process group on exit to prevent stale Node/Python processes
- Register cleanup handlers on SIGINT/SIGTERM for reliable teardown
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
(cherry picked from commit 93562d65ff66631c24f57dfc79a17c4fe4b3658a)
The useInput hook and TextInput's onSubmit both fired on Enter,
causing every user message to be submitted twice. Remove the
duplicate key.return handler in useInput — TextInput already
handles normal Enter submission.
A lightweight open-source Python implementation of the Agent Harness architecture.
44x lighter than Claude Code (11K vs 512K lines), 98% core tool coverage.
- 43 tools with Pydantic validation and parallel execution
- Skills system compatible with anthropics/skills (17+ tested)
- Plugin system compatible with claude-code/plugins (12+ tested)
- API retry with exponential backoff
- Multi-level permissions with path rules
- React/Ink TUI with "Oh my Harness!" branding
- 114 unit tests + 6 E2E test suites
- MIT License