hermes-agent/tests
WideLee de584cd1dd feat(qqbot): add inline-keyboard approvals and update prompts
The QQ Bot v2 API supports inline keyboards on outbound messages. When a
user taps a button, the platform dispatches an INTERACTION_CREATE
gateway event; the bot ACKs it via PUT /interactions/{id} and decodes
the button's data payload to route the click.

This commit adds:

New module gateway/platforms/qqbot/keyboards.py

- Inline-keyboard dataclasses (InlineKeyboard, KeyboardRow, KeyboardButton,
  KeyboardButtonAction, KeyboardButtonRenderData, KeyboardButtonPermission)
  that serialize to the JSON shape the QQ API expects.
- build_approval_keyboard(session_key) — 3-button layout:
   允许一次 /  始终允许 /  拒绝, all sharing group_id='approval'
  so clicking one greys out the rest.
- build_update_prompt_keyboard() — Yes/No keyboard for update confirms.
- parse_approval_button_data() / parse_update_prompt_button_data() —
  decode the button_data payload from INTERACTION_CREATE.
  approve:<session_key>:<decision>  (decision = allow-once|allow-always|deny)
  update_prompt:<answer>            (answer = y|n)
- build_approval_text(ApprovalRequest) — markdown renderer for the
  surrounding message body (exec-approval and plugin-approval variants,
  with severity icons 🔴/🔵/🟡).
- parse_interaction_event(raw) → InteractionEvent dataclass — normalizes
  the nested raw payload (id / scene / openids / button_data / etc.).

Adapter changes (gateway/platforms/qqbot/adapter.py)

- _dispatch_payload routes INTERACTION_CREATE → _on_interaction.
- _on_interaction parses the event, ACKs via PUT /interactions/{id}, then
  invokes a user-registered interaction callback. Exceptions from the
  callback are caught and logged (never propagate into the WS loop).
- set_interaction_callback(cb) lets gateway wiring register a routing
  handler that inspects button_data and resolves the corresponding
  pending approval / update prompt.
- _send_c2c_text / _send_group_text now accept an optional keyboard kwarg
  and append it to the outbound body.
- send_with_keyboard(chat_id, content, keyboard, reply_to=None) — public
  helper that sends a single short message with a keyboard attached.
  Does NOT chunk-split (a keyboard message has one interactive surface).
  Guild chats are rejected non-retryably — they don't support keyboards.
- send_approval_request(chat_id, ApprovalRequest, reply_to=None) +
  send_update_prompt(chat_id, content, reply_to=None) — convenience
  wrappers over send_with_keyboard.

Tests

27 new unit tests under TestApprovalButtonData, TestUpdatePromptButtonData,
TestBuildApprovalKeyboard, TestBuildUpdatePromptKeyboard, TestBuildApprovalText,
TestInteractionEventParsing, and TestAdapterInteractionDispatch. Cover:

- Button-data round-trip (build → parse returns original session/decision)
- Keyboard JSON shape + mutual-exclusion group_id
- Exec vs plugin approval text templates + severity icons
- Interaction event parsing (c2c / group / guild scene codes)
- _on_interaction end-to-end: ACK invoked, callback receives parsed event,
  callback exceptions are swallowed, missing id skips ACK, no registered
  callback is harmless.

Full qqbot suite: 118 passed (72 existing + 19 chunked + 27 keyboards).

Co-authored-by: WideLee <limkuan24@gmail.com>
2026-05-07 07:36:30 -07:00
..
acp fix(acp): preserve assistant reasoning metadata in session persistence 2026-05-05 10:18:28 -07:00
acp_adapter fix(acp): run /steer as a regular prompt on idle sessions (#18258) 2026-04-30 22:45:14 -07:00
agent fix(compressor): soften summary prompt for content filters 2026-05-07 06:42:32 -07:00
cli refactor: replace 'cmp' text with 🗜️ emoji in status bar 2026-05-07 05:27:45 -07:00
cron test(cron): update _isolate_tick_lock fixture for _get_lock_paths 2026-05-05 09:57:06 -07:00
e2e fix(gateway): move quick-command dispatch before built-in handlers 2026-05-04 01:39:23 -07:00
environments/benchmarks
fakes
gateway feat(qqbot): add inline-keyboard approvals and update prompts 2026-05-07 07:36:30 -07:00
hermes_cli feat(kanban): per-task max_retries override (#20263 follow-up, supersedes #20972) (#21330) 2026-05-07 07:29:02 -07:00
hermes_state fix(resume): redirect --resume to the descendant that actually holds the messages 2026-04-24 03:04:42 -07:00
honcho_plugin feat(honcho): explain why when honcho_profile returns an empty card 2026-04-27 12:37:33 -07:00
integration
openviking_plugin fix(openviking): pre-check fs/stat to route file URIs before hitting directory-only endpoints 2026-04-30 02:35:29 -07:00
plugins fix(openviking): add Bearer auth header and omit empty/legacy tenant headers (#21232) 2026-05-07 05:45:58 -07:00
providers feat(providers): make all 33 providers pluggable under plugins/model-providers/ 2026-05-05 13:40:01 -07:00
run_agent fix(agent): honor configured model max tokens 2026-05-07 06:40:30 -07:00
skills fix(google-workspace): restore required_credential_files in SKILL.md (#16452) 2026-05-04 12:43:14 -07:00
stress feat(kanban): durable multi-profile collaboration board (#17805) 2026-04-30 13:36:47 -07:00
tools fix(mcp): surface image tool results as MEDIA tags instead of dropping them (#21328) 2026-05-07 07:14:16 -07:00
tui_gateway fix(tui): close slash parity gaps with CLI (#20339) 2026-05-05 15:42:39 -05:00
website docs(skills): explain restoring bundled skills 2026-05-05 13:46:20 -07:00
__init__.py
conftest.py fix(tests): avoid asyncio DeprecationWarning in event loop fixture on 3.12+ 2026-05-07 07:05:05 -07:00
run_interrupt_test.py
test_account_usage.py feat(account-usage): add per-provider account limits module 2026-04-21 01:56:35 -07:00
test_atomic_replace_symlinks.py refactor: consolidate symlink-safe atomic replace into shared helper 2026-04-28 04:58:22 -07:00
test_base_url_hostname.py security(runtime_provider): close OLLAMA_API_KEY substring-leak sweep miss (#13522) 2026-04-21 06:06:16 -07:00
test_batch_runner_checkpoint.py test: regression coverage for checkpoint dedup and inf/nan coercion 2026-04-24 14:32:21 -07:00
test_cli_file_drop.py fix(tui): improve macOS paste and shortcut parity 2026-04-21 08:00:00 -07:00
test_cli_manual_compress.py test(cli): regression test for manual /compress system_message 2026-04-28 05:21:49 -07:00
test_cli_skin_integration.py fix(ci): stabilize main test suite regressions (#17660) 2026-04-29 23:18:55 -07:00
test_ctx_halving_fix.py
test_empty_model_fallback.py
test_evidence_store.py
test_get_tool_definitions_cache_isolation.py fix(tools): isolate get_tool_definitions quiet_mode cache + dedup LCM injection (#17335) 2026-04-30 04:32:06 -07:00
test_hermes_constants.py
test_hermes_home_profile_warning.py fix(constants): warn once when get_hermes_home() falls back under an active profile (#18746) 2026-05-02 01:49:55 -07:00
test_hermes_logging.py fix(logging): attach gateway log after cli init 2026-04-26 19:01:26 -07:00
test_hermes_state.py fix(telegram): polish topic mode — CASCADE, General-topic handling, rename guard, debounce 2026-05-04 12:07:17 -07:00
test_honcho_client_config.py
test_install_sh_pythonpath_sanitization.py fix: harden install.sh against inherited Python env leakage 2026-05-06 04:02:02 -07:00
test_install_sh_setup_wizard_tty_probe.py fix(install): widen /dev/tty open-probe to sibling gates (#16746) 2026-04-28 06:45:55 -07:00
test_ipv4_preference.py
test_lazy_session_regressions.py fix: resolve lazy session creation regressions (#18370 fallout) (#20363) 2026-05-06 01:11:49 +05:30
test_mcp_serve.py fix(mcp): coerce numeric tool args defensively 2026-05-07 07:17:12 -07:00
test_mini_swe_runner.py fix(kimi): omit temperature entirely for Kimi/Moonshot models (#13157) 2026-04-20 12:23:05 -07:00
test_minimax_model_validation.py fix(models): validate MiniMax models against static catalog (#12611, #12460, #12399, #12547) 2026-04-19 22:44:47 -07:00
test_minimax_oauth.py test(cli): cover minimax-oauth resolution, refresh, menu wiring 2026-04-29 09:53:42 -07:00
test_minisweagent_path.py
test_model_picker_scroll.py
test_model_tools.py fix(plugins): stop firing pre_tool_call hook twice per tool execution (#17611) 2026-04-29 12:43:39 -07:00
test_model_tools_async_bridge.py fix(model_tools): cancel coroutine on timeout so worker thread exits + log full traceback 2026-04-29 05:00:40 -07:00
test_ollama_num_ctx.py
test_packaging_metadata.py
test_plugin_skills.py fix(skills): support category-qualified local skill names 2026-05-05 10:15:31 -07:00
test_process_loop_event_loop_warning.py fix(cli): replace get_event_loop() with get_running_loop() to silence RuntimeWarning in process_loop thread (#19285) 2026-05-07 06:35:54 -07:00
test_project_metadata.py build(deps): add qrcode to dingtalk + feishu extras (parity with messaging) (#11627) 2026-04-17 13:31:53 -07:00
test_retry_utils.py
test_sql_injection.py
test_subprocess_home_isolation.py
test_timezone.py test: speed up slow tests (backoff + subprocess + IMDS network) (#11797) 2026-04-17 14:21:22 -07:00
test_toolset_distributions.py
test_toolsets.py fix: merge plugin tools into builtin toolsets 2026-05-05 10:14:17 -07:00
test_trajectory_compressor.py fix(kimi): omit temperature entirely for Kimi/Moonshot models (#13157) 2026-04-20 12:23:05 -07:00
test_trajectory_compressor_async.py fix(kimi): omit temperature entirely for Kimi/Moonshot models (#13157) 2026-04-20 12:23:05 -07:00
test_transform_llm_output_hook.py test+docs: cover transform_llm_output hook + release author map 2026-05-07 05:46:05 -07:00
test_transform_tool_result_hook.py test: stop testing mutable data — convert change-detectors to invariants (#13363) 2026-04-20 23:20:33 -07:00
test_tui_gateway_server.py fix(tui): render structured content on resume 2026-05-07 06:37:23 -07:00
test_utils_truthy_values.py
test_yuanbao_integration.py yuanbao platform (#16298) 2026-04-26 18:50:49 -07:00
test_yuanbao_markdown.py yuanbao platform (#16298) 2026-04-26 18:50:49 -07:00
test_yuanbao_pipeline.py yuanbao platform (#16298) 2026-04-26 18:50:49 -07:00
test_yuanbao_proto.py yuanbao platform (#16298) 2026-04-26 18:50:49 -07:00