feat(commands): slash command parity with hermes-agent (batch 1) - #618
feat(commands): slash command parity with hermes-agent (batch 1)#618renheqiang wants to merge 16 commits into
Conversation
Two near-simultaneous /api/session/retry calls could both read the same history, both compute the same last_user_idx, and the second write would overwrite the first or double-truncate the transcript. Wrap the read+mutate of s.messages in `with LOCK:` to serialize concurrent retries. LOCK is a non-reentrant threading.Lock and both get_session() and Session.save() (via _write_session_index()) acquire it internally, so they remain outside the new critical section to avoid self-deadlock. Persistence after lock release is last-write-wins on a consistent post-mutation state. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…calHandler Adds two read-only aggregator endpoints: - GET /api/session/status -> session id, title, model, workspace, personality, message count, agent_running (active stream check) - GET /api/session/usage -> input/output/total token counts + cost Frontend additions/changes: - cmdStatus (new) -- renders /status output as an assistant message - cmdUsage (replaced) -- now displays current usage instead of toggling the show_token_usage setting; that toggle remains in Settings - cmdCompact -- registered directly in HANDLERS (drops the parseCommand _localHandler escape hatch flagged in Task 2 review); compact moves into WEBUI_ONLY_COMMANDS so the dropdown advertises it Cleanup: - WEBUI_ALIASES map deleted (only entry was compact->compress, replaced by direct HANDLERS registration) - _localHandler branch removed from executeCommand - compact removed from UNSUPPORTED_IN_WEBUI (it is no longer a registry alias; it is a webui-only command) Tests: 14/14 in tests/test_session_ops.py (4 new for status + usage).
…h; backfill new i18n keys Task 2 of slash-command-parity batch 1 changed two invariants that existing tests still asserted against the old shape: - The /skills entry was hardcoded in the COMMANDS array; it is now sourced from /api/commands at runtime, so the literal name:'skills' string is gone from commands.js. The regression test now asserts the new invariant: cmdSkills function exists and HANDLERS.skills is registered (which is what actually has to be true for /skills to dispatch correctly). - The 3 new keys (cmd_not_supported_yet, cmd_compress_deferred, cmd_webui_only_session) were only added to the en and zh locale blocks. Backfill them into es, de, and zh-Hant so the Spanish-locale-coverage regression test (and parity with all other locales) holds.
Document the manual QA steps for the batch-1 slash command parity work (tasks 1-7): /help, /new, /clear, /title, /status, /usage, /stop, /retry, /undo, /model, /personality, /skills, /theme, /workspace. Includes deferred-command behavior expectations and bridged-CLI-session edge cases.
Replaces hardcoded English literals in cmdStop, cmdTitle, cmdRetry,
cmdUndo, cmdStatus, and cmdUsage with t('key') lookups so the slash
command output respects the active locale. Adds 32 new keys covering
status/usage panels and the missing toast messages across all five
locale blocks (en, es, de, zh, zh-Hant).
…ick instead of leaking to LLM The previous implementation put /yolo, /reasoning, /fast, /compress in UNSUPPORTED_IN_WEBUI which filters them out of REGISTRY entirely. Result: typing /yolo manually fell through to send() and got dispatched as plain text to the LLM -- the exact silent-forward failure mode the parity spec calls out as critical to avoid. Move these into the 'in-registry-but-no-handler' bucket so executeCommand toasts 'not yet supported in web UI' instead. Caught by Playwright checklist test (sections 8.22, 8.23). UNSUPPORTED_IN_WEBUI now contains only truly-irrelevant CLI concepts (voice, paste, image, skin, browser, plan, config, etc.) that have no sensible meaning in the web UI.
|
Thanks for this thorough PR — the scope is well-defined, the commit history is clean and readable, and the safety guarantee around deferred commands is genuinely important to fix. Architecture ✅The Behaviour fixes ✅
Specific review questions / items to verify1. Lock pattern in The 2. The test 3. If 4. The commit note says this now contains "truly-irrelevant CLI concepts (voice, paste, image, skin, browser, plan, config, etc.)". One question: 5. i18n completeness The commit message mentions 32 new keys across 5 locales (en, es, de, zh, zh-Hant). The regression test confirms Spanish coverage — but is there a test for de, zh, zh-Hant key completeness, or is it a manual check? Worth noting if it's manual so it doesn't silently regress when batch-2 adds more keys. 6. Playwright checklist "29/29 passed" is great. Is this checklist automated (part of CI) or manual? If manual, it would be good to note that in SummaryThis is a solid, well-scoped PR. The safety fix (deferred commands toast instead of leaking to LLM) alone is worth merging. Items 1–2 are the most important to confirm before merge; items 3–6 are nice-to-have or follow-up candidates. Happy to approve after the lock comment and |
|
Thanks for this — the command parity work is solid in scope and the JS all passes Required:
Advisory (non-blocking):
Happy to re-review once those three are addressed. |
…voice toast
Addresses the three required fixes from the prior review pass:
Blocker 1: stale-object TOCTOU in retry_last/undo_last
On a SESSIONS cache miss, two concurrent get_session() calls can each
load and cache a different Session instance for the same session_id
(the second store clobbers the first). Both threads then enter
`with LOCK:` serially but mutate different in-memory objects, and the
second s.save() overwrites the first with stale data.
Fix: re-bind `s = SESSIONS.get(session_id, s)` inside the lock so
both threads converge on the canonical cached instance. The `, s)`
fallback handles the case where the cache was evicted between
get_session() and the lock acquisition.
Blocker 2: test_retry_does_not_double_append was sequential, not concurrent
Added test_retry_concurrent_requests_are_safe which fires 4 concurrent
/api/session/retry calls via ThreadPoolExecutor and asserts the
resulting transcript is a strict prefix of the original (never has a
phantom duplicate of the resent message). The test is structural — it
pins the invariant rather than racing for a specific failure mode.
Blocker 3: /voice should be a deferred command (toast), not UNSUPPORTED_IN_WEBUI
WebUI does have voice input via the mic button (#btnMic) backed by the
Web Speech API + MediaRecorder fallback. The /voice slash command now:
- removed from UNSUPPORTED_IN_WEBUI
- registered as cmdVoice handler
- clicks the mic button if visible (auto-trigger)
- else toasts cmd_voice_use_mic pointing the user to it
Added 5 i18n translations (en, es, de, zh, zh-TW).
Tests: 1348 passed, 47 skipped, 0 failures.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Independent End-to-End Review — PR #618Independent review picking up the 3 blockers from the prior review. All three pushed in commit TL;DRNow merge-ready. All three required fixes from the second review pass are now in the branch. Tests green (1348 passed, 0 failed). One remaining concern (CHANGELOG/version bump) deferred to merge time given the multi-PR coordination already discussed. Blocker resolution status —
|
| Aspect | Status |
|---|---|
| Tests | ✅ 1348 passed, 0 failed |
| Blocker 1: TOCTOU | ✅ Fixed in d830a1e |
| Blocker 2: concurrent test | ✅ Added in d830a1e |
| Blocker 3: /voice deferred | ✅ Improved (auto-clicks mic when available) |
| Security | ✅ Clean |
| CHANGELOG / version | ⏳ At merge time (multi-PR coordination) |
All three blockers from the prior review are now actually addressed. Thanks @renheqiang for the substantial command-parity work — happy to push one more round of fixes if the maintainer flags anything else on a final pass.
Combines PR #618 (@renheqiang) and PR #701 (@franksong2702). From #618 — slash command parity with hermes-agent: - New commands: /stop, /title, /retry, /undo, /status, /voice - New api/commands.py (GET /api/commands endpoint) - New api/session_ops.py (session retry, undo, status, usage handlers) - i18n strings for all new commands From #701 — skills in slash autocomplete: - Skills from /api/skills appear in / dropdown with Skill badge - Built-in commands take precedence on name collisions - Lazy-loaded, cache-backed, race-safe 1469 tests pass. Closes #460. Co-authored-by: renheqiang <renheqiang@users.noreply.github.com> Co-authored-by: franksong2702 <franksong2702@users.noreply.github.com>
|
Thank you @renheqiang! Your slash command parity work has been combined with PR #701 (@franksong2702) into PR #711. Both contributors are credited. The combined PR brings /stop, /title, /retry, /undo, /status, /voice commands + skill autocomplete — 862 lines, 27 new tests, all passing. |
Combines PR #618 (@renheqiang) slash command parity (/retry /undo /stop /title /status /voice) with PR #701 (@franksong2702) skill autocomplete. 1469 tests pass. Closes #460. Co-authored-by: renheqiang <renheqiang@users.noreply.github.com> Co-authored-by: franksong2702 <franksong2702@users.noreply.github.com>
…na#711) Combines PR nesquena#618 (@renheqiang) slash command parity (/retry /undo /stop /title /status /voice) with PR nesquena#701 (@franksong2702) skill autocomplete. 1469 tests pass. Closes nesquena#460. Co-authored-by: renheqiang <renheqiang@users.noreply.github.com> Co-authored-by: franksong2702 <franksong2702@users.noreply.github.com>
…na#711) Combines PR nesquena#618 (@renheqiang) slash command parity (/retry /undo /stop /title /status /voice) with PR nesquena#701 (@franksong2702) skill autocomplete. 1469 tests pass. Closes nesquena#460. Co-authored-by: renheqiang <renheqiang@users.noreply.github.com> Co-authored-by: franksong2702 <franksong2702@users.noreply.github.com>
Summary
GET /api/commandsexposes hermes-agent'sCOMMAND_REGISTRYto the webui;static/commands.jsloads dynamically at boot — future agent commands appear in the dropdown automatically (single source of truth, no drift)/retry,/undo,/stop,/title,/status— behavior parity withgateway/run.py:_handle_*_command/usagenow displays current token usage (was: toggling a settings flag)/compactnow toasts "deferred — use CLI for now" (was: silently sending free text to the LLM asking it to compress)/yolo,/reasoning,/fast,/compress, etc.) now toast"Web UI not yet supported"instead of being silently forwarded to the LLM as plain text — eliminates the silent-failure mode where the model could invent fake tool callsDeferred to a later batch (state lives in the agent process — needs an IPC channel to implement properly):
/yolo,/reasoning,/fast,/compress,/voice,/branch,/rollback,/snapshot,/resume,/btw,/background,/queue,/profile,/provider,/insights,/debug,/reload,/reload-mcp,/approve,/deny. These show in the dropdown but toast "not yet supported" rather than running.Test plan
tests/test_commands_endpoint.py(7) +tests/test_session_ops.py(14)tests/test_regressions.py::test_skills_slash_command_definedupdated for the registry-driven model/yolo,/reasoning,/compactTESTING.mdunder "Slash command parity"/retryno-double-append guard verified by automated test (test_retry_does_not_double_append)retry_last/undo_lastto prevent concurrent read-modify-write races (lock pattern documented inline becauseSession.save()re-acquires the same non-reentrant LOCK and would deadlock if held)Notes for reviewer
nesquena/masterand rebased cleanUNSUPPORTED_IN_WEBUI(which filters them out of the registry entirely), causing/yoloetc. to fall through tosend()and get dispatched as plain text. Moved them to the "in-registry-but-no-handler" bucket so they toast properly. See commit6fd0c14.🤖 Generated with Claude Code