Skip to content

feat(slack): render clarify prompts as Block Kit buttons - #28885

Closed
cypres0099 wants to merge 1 commit into
NousResearch:mainfrom
cypres0099:feat/slack-clarify-buttons
Closed

feat(slack): render clarify prompts as Block Kit buttons#28885
cypres0099 wants to merge 1 commit into
NousResearch:mainfrom
cypres0099:feat/slack-clarify-buttons

Conversation

@cypres0099

@cypres0099 cypres0099 commented May 19, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

Adds native Slack Block Kit buttons for multi-choice clarify prompts, bringing Slack to parity with Telegram and Discord. A choice click resolves the waiting clarify request; Other switches the existing gateway flow into free-text capture. Open-ended clarifies continue to use the base adapter's text behavior.

The refreshed implementation incorporates both issues from the automated maintainer sweep: clarify actions reuse Slack's shared interactive-user authorization policy, and long questions are budgeted under Slack's 3000-character section-text limit.

Related Issue

N/A — platform parity feature; no separate upstream issue is open.

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)
  • ✨ New feature (non-breaking change that adds functionality)
  • 🔒 Security fix
  • 📝 Documentation update
  • ✅ Tests (adding or improving test coverage)
  • ♻️ Refactor (no behavior change)
  • 🎯 New skill (bundled or hub)

Changes Made

  • plugins/platforms/slack/adapter.py
    • Adds send_clarify() Block Kit rendering with unique per-choice action IDs and an Other action.
    • Adds atomic action-resolution guarding and message updates after a decision.
    • Routes clarify clicks through _is_interactive_user_authorized, preserving GatewayRunner auth and global gateway allowlist policy.
    • Budgets/truncates long question text under Slack's 3000-character section-block cap.
  • tests/gateway/test_slack_clarify_buttons.py
    • Covers rendering, open-ended fallback, thread inheritance, choice/Other resolution, double-clicks, malformed and missing entries, unique action IDs, shared auth policy, and long-question truncation.

How to Test

  1. Run HOME=/tmp/hermes-ci-home scripts/run_tests.sh -j 1 tests/gateway/test_slack_clarify_buttons.py tests/gateway/test_slack_approval_buttons.py.
  2. Confirm all 41 tests pass.
  3. In Slack, trigger a multi-choice clarify, click a choice, and confirm the message records the decision and the agent continues; verify Other accepts the next free-text reply.

Checklist

Code

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits (fix(scope):, feat(scope):, etc.)
  • I searched for existing PRs to make sure this isn't a duplicate
  • My PR contains only changes related to this fix/feature (no unrelated commits)
  • I've run pytest tests/ -q and all tests pass
  • I've added tests for my changes (required for bug fixes, strongly encouraged for features)
  • I've tested on my platform: macOS, Python 3.11

The canonical full suite was run on the same current upstream base via the #30592 control branch. Its failures/timeouts were outside this two-file diff and matched current-main macOS/environment-sensitive areas. The Slack clarify and existing approval-button files pass 41/41.

Documentation & Housekeeping

  • I've updated relevant documentation (README, docs/, docstrings) — or N/A
  • I've updated cli-config.yaml.example if I added/changed config keys — or N/A
  • I've updated CONTRIBUTING.md or AGENTS.md if I changed architecture or workflows — or N/A
  • I've considered cross-platform impact (Windows, macOS) per the compatibility guide — or N/A
  • I've updated tool descriptions/schemas if I changed tool behavior — or N/A

Screenshots / Logs

Slack clarify + approval buttons: 41 passed, 0 failed
ruff check .: passed
git diff --check upstream/main...HEAD: passed
check-windows-footguns.py --diff upstream/main: passed (2 files scanned)

@alt-glitch alt-glitch added type/feature New feature or request comp/gateway Gateway runner, session dispatch, delivery platform/slack Slack app adapter P2 Medium — degraded but workaround exists labels May 19, 2026
@cypres0099

Copy link
Copy Markdown
Contributor Author

CI notes:

check-attribution failure — fixed by sibling PR #28897 (one-line AUTHOR_MAP addition). Will re-run on this PR after #28897 merges.

test failures — all 9 pre-exist on upstream/main, unrelated to this PR's diff:

  • tests/hermes_cli/test_gateway_service.py::test_gateway_install_passes_system_flagspytest: reading from stdin while output is captured
  • tests/hermes_cli/test_gateway_wsl.py::test_install_wsl_with_systemd_warns — same stdin issue
  • tests/gateway/test_restart_resume_pending.py::test_clean_drain_does_not_mark_resume_pending — telegram session state assertion
  • tests/gateway/test_restart_resume_pending.py::test_drain_timeout_only_marks_still_running_sessions — telegram session state assertion
  • tests/tools/test_file_operations.py::TestGitBaselineCheck::*'ShellFileOperations' object has no attribute '_check_git_baseline'

None touch gateway/platforms/slack.py or tests/gateway/test_slack_*. Happy to rebase if you'd like, but the diff itself is green:

scripts/run_tests.sh tests/gateway/test_slack_clarify_buttons.py tests/gateway/test_slack_approval_buttons.py
==> 70 passed

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the Slack clarify parity work. I verified the premise still holds on current main: SlackAdapter has no send_clarify override, so BasePlatformAdapter.send_clarify still renders multi-choice clarifies as numbered text (gateway/platforms/base.py:2543).

Problems

  • The new clarify button handler checks only SLACK_ALLOWED_USERS (gateway/platforms/slack.py:2707 in the PR). Current main hardened Slack interactive actions through _is_interactive_user_authorized (gateway/platforms/slack.py:3026, :3205 on origin/main), which also honors GatewayRunner auth and GATEWAY_ALLOWED_USERS/GATEWAY_ALLOW_ALL_USERS. Clarify should use the same helper so button clicks do not bypass current auth policy.
  • The clarify section block sends f"❓ {question}" directly (gateway/platforms/slack.py:2660 in the PR). Current Slack approval/confirm paths budget section text for Slack's 3000-character Block Kit cap (gateway/platforms/slack.py:2879, :2965 on origin/main), and tools/clarify_tool.py does not cap question length.

Suggested changes

  • Reuse _is_interactive_user_authorized in _handle_clarify_action and add a global/GatewayRunner auth test.
  • Budget/truncate the question block and add a long-question invalid_blocks regression test.

Automated hermes-sweeper review.

Comment thread gateway/platforms/slack.py Outdated
Comment thread gateway/platforms/slack.py Outdated
@cypres0099
cypres0099 force-pushed the feat/slack-clarify-buttons branch from a1a4d19 to eb38b7b Compare June 16, 2026 13:37
@cypres0099

Copy link
Copy Markdown
Contributor Author

Refreshed this branch onto latest main and addressed the sweeper feedback.\n\nWhat changed:\n- Clarify button clicks now reuse Slack's shared _is_interactive_user_authorized path, so they honor GatewayRunner auth plus GATEWAY_ALLOWED_USERS/GATEWAY_ALLOW_ALL_USERS instead of only SLACK_ALLOWED_USERS.\n- Long clarify questions are budgeted under Slack's 3000-character section-block cap to avoid invalid_blocks.\n- Added regression tests for GatewayRunner auth rejection, global gateway allowlist authorization, and long-question truncation.\n- Rewrote the refreshed commit with my current GitHub noreply author, so it should no longer depend on the separate AUTHOR_MAP PR for attribution.\n\nLocal verification:\n- scripts/run_tests.sh tests/gateway/test_slack_clarify_buttons.py tests/gateway/test_slack_approval_buttons.py -> 40 passed\n- uv tool run ruff check gateway/platforms/slack.py tests/gateway/test_slack_clarify_buttons.py -> passed\n\nCI is running now on the refreshed branch.

@cypres0099
cypres0099 force-pushed the feat/slack-clarify-buttons branch from eb38b7b to 90bae88 Compare July 13, 2026 20:50
@cypres0099

Copy link
Copy Markdown
Contributor Author

Final refresh complete at 90bae8898: shared Slack interactive auth and the 3000-character question budget are covered by regressions, both review threads are resolved, all required CI checks pass, and GitHub reports the PR CLEAN/MERGEABLE.

teknium1 pushed a commit that referenced this pull request Jul 22, 2026
Slack now overrides send_clarify to render multi-choice clarify prompts
as native Block Kit buttons (one per choice + a final '✏️ Other…'
free-text button), mirroring the Telegram/Discord adapters and the
existing Slack approval-button pattern.

- Unique hermes_clarify_choice_<idx> action_ids (Slack rejects
  duplicate action_ids within one actions block); dispatch via a
  compiled-regex action matcher plus hermes_clarify_other.
- Chunks elements across actions blocks in groups of 5 so a larger
  choice list degrades gracefully instead of 400ing (invalid_blocks).
- Choice taps resolve through tools.clarify_gateway
  .resolve_gateway_clarify with the canonical registered choice text —
  the same applier the typed-reply path uses — then edit the message
  to show the outcome and drop the buttons.
- 'Other' flips the entry into text-capture via mark_awaiting_text
  (only on tap, never at send time) so the gateway text-intercept
  captures the next typed message.
- Auth-gated via _is_interactive_user_authorized; atomic-pop
  double-click guard mirrors _approval_resolved; late taps on evicted
  entries surface an honest expiry notice instead of a false ✓.
- Open-ended prompts delegate to the base plain-text render.

Salvaged from PR #61943 by @100yenadmin. Earliest implementation of
this feature was PR #28885 by @cypres0099; sibling implementations
#66606 (@jaaro-ai) and #51547 (@Mongol-Jimmi) are superseded.

Closes #52369
teknium1 pushed a commit that referenced this pull request Jul 22, 2026
Slack now overrides send_clarify to render multi-choice clarify prompts
as native Block Kit buttons (one per choice + a final '✏️ Other…'
free-text button), mirroring the Telegram/Discord adapters and the
existing Slack approval-button pattern.

- Unique hermes_clarify_choice_<idx> action_ids (Slack rejects
  duplicate action_ids within one actions block); dispatch via a
  compiled-regex action matcher plus hermes_clarify_other.
- Chunks elements across actions blocks in groups of 5 so a larger
  choice list degrades gracefully instead of 400ing (invalid_blocks).
- Choice taps resolve through tools.clarify_gateway
  .resolve_gateway_clarify with the canonical registered choice text —
  the same applier the typed-reply path uses — then edit the message
  to show the outcome and drop the buttons.
- 'Other' flips the entry into text-capture via mark_awaiting_text
  (only on tap, never at send time) so the gateway text-intercept
  captures the next typed message.
- Auth-gated via _is_interactive_user_authorized; atomic-pop
  double-click guard mirrors _approval_resolved; late taps on evicted
  entries surface an honest expiry notice instead of a false ✓.
- Open-ended prompts delegate to the base plain-text render.

Salvaged from PR #61943 by @100yenadmin. Earliest implementation of
this feature was PR #28885 by @cypres0099; sibling implementations
#66606 (@jaaro-ai) and #51547 (@Mongol-Jimmi) are superseded.

Closes #52369
@teknium1

Copy link
Copy Markdown
Contributor

Closing as superseded by #69318 (merged): you were the EARLIEST implementation of clarify buttons (May 19) and are credited as originator in #69318; the landed base (#61943) added >5-choice chunking, expiry UX, and mrkdwn escaping on the same architecture you pioneered.

Thanks for digging into this — the consolidated fix stands on the cluster's collective analysis, and your work is credited in #69318's summary.

@teknium1 teknium1 closed this Jul 22, 2026
randlee pushed a commit to randlee/hermes-agent that referenced this pull request Aug 11, 2026
Slack now overrides send_clarify to render multi-choice clarify prompts
as native Block Kit buttons (one per choice + a final '✏️ Other…'
free-text button), mirroring the Telegram/Discord adapters and the
existing Slack approval-button pattern.

- Unique hermes_clarify_choice_<idx> action_ids (Slack rejects
  duplicate action_ids within one actions block); dispatch via a
  compiled-regex action matcher plus hermes_clarify_other.
- Chunks elements across actions blocks in groups of 5 so a larger
  choice list degrades gracefully instead of 400ing (invalid_blocks).
- Choice taps resolve through tools.clarify_gateway
  .resolve_gateway_clarify with the canonical registered choice text —
  the same applier the typed-reply path uses — then edit the message
  to show the outcome and drop the buttons.
- 'Other' flips the entry into text-capture via mark_awaiting_text
  (only on tap, never at send time) so the gateway text-intercept
  captures the next typed message.
- Auth-gated via _is_interactive_user_authorized; atomic-pop
  double-click guard mirrors _approval_resolved; late taps on evicted
  entries surface an honest expiry notice instead of a false ✓.
- Open-ended prompts delegate to the base plain-text render.

Salvaged from PR NousResearch#61943 by @100yenadmin. Earliest implementation of
this feature was PR NousResearch#28885 by @cypres0099; sibling implementations
NousResearch#66606 (@jaaro-ai) and NousResearch#51547 (@Mongol-Jimmi) are superseded.

Closes NousResearch#52369
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/gateway Gateway runner, session dispatch, delivery P2 Medium — degraded but workaround exists platform/slack Slack app adapter sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants