Skip to content

OctavianTocan/openclaw-menu-handler

Repository files navigation

🗡️ openclaw-menu-handler

Instant inline keyboard menu for OpenClaw Telegram. Sub-second response — no LLM turn needed.

/menu → instant buttons. Every tap → instant response. The agent never wakes up for menu interactions.

What it does

Registers a /menu command and a menu:* interactive handler at the OpenClaw gateway level. When you type /menu in Telegram, the plugin responds directly with an inline keyboard — categories, submenus, skill launchers. Button callbacks are handled the same way. Zero LLM latency.

Features

  • LLM-bypass — command and callbacks handled at gateway, not routed through the agent
  • 📊 Dynamic categories — "Recent" and "Top Used" populated from skill-tracker usage data
  • 🗂️ Submenus — Content & Writing, Infrastructure, Memory & Recall, Notes & Docs, Development, Search & Tools
  • 🎯 Skill launcher — tapping a skill button fires the corresponding /skill-name command
  • 🔒 No agent context cost — menu interactions don't consume tokens or context window

Installation

# Clone into your OpenClaw extensions directory
cd ~/.openclaw/extensions
git clone https://github.com/OctavianTocan/openclaw-menu-handler.git
cd openclaw-menu-handler
pnpm install
pnpm build

# Add to your openclaw.json plugins
# "plugins": { "allow": ["menu-handler"] }

# Restart the gateway
openclaw gateway restart

Usage

Type /menu in any Telegram chat connected to your OpenClaw agent. Tap buttons to navigate submenus or launch skills.

Customization

Menu items are loaded from a menu.json file in the plugin directory. Copy the example and edit it:

cp menu.example.json menu.json

The config file defines a title and an array of categories, each with a label, key, and button items:

{
  "title": "🤖 My Agent",
  "categories": [
    {
      "label": "🔧 Tools",
      "key": "tools",
      "items": [
        { "text": "My Skill", "command": "/my-skill" },
        { "text": "Other", "command": "/other" }
      ]
    }
  ]
}

menu.json is gitignored so your personal commands stay local. The "Recent" and "Top Used" dynamic categories are always available regardless of config.

Architecture

Button delivery (Telegram-specific)

Telegram native commands read buttons from result.channelData?.telegram?.buttons, not result.interactive. This was discovered through source-level debugging — having interactive in the response actively prevents button delivery because OpenClaw's isEditableTelegramProgressResult() returns false when it detects that field.

Button format: Array<Array<{ text: string, callback_data: string }>> — rows of buttons, matching Telegram's inline_keyboard schema.

Interactive handler

Registered under the menu namespace. Callbacks (menu:content, menu:back, menu:recent, etc.) are resolved by the handler using ctx.respond.editMessage(), which edits the existing message in-place — no message spam.

Skill usage data

Reads ~/.openclaw/skill-usage.db (JSONL, written by the skill-tracker plugin) to populate the "Recent" and "Top Used" dynamic categories. No database dependency — just line-delimited JSON.

Testing

pnpm test

The test suite uses Vitest and runs entirely in Node without any external dependencies.

File What it covers
test/skill-usage.test.ts Core JSONL parsing, deduplication, sorting, limits
test/skill-usage-extended.test.ts Field fidelity, multi-agent data, path override API, large DB (10k entries), edge cases
test/menus.test.ts itemsToButtons, buildMenuFromConfig, fallback menus, dynamic submenus
test/menus-extended.test.ts Unicode titles, empty categories, even/odd pairing, submenu isolation, 20-category stress
test/handler.test.ts Core handler routing, plugin registration, /menu command shape
test/handler-extended.test.ts Group/forum context, repeated calls, response shape contract, /menu button delivery invariants

All tests use temp directories and isolated skill DB overrides — they leave no trace on your real ~/.openclaw/skill-usage.db.

One test specifically guards the known Telegram button-delivery regression: it asserts that the /menu command result never has a result.interactive field, because its presence causes OpenClaw's isEditableTelegramProgressResult() to return false and silently skip button injection.

Requirements

  • OpenClaw ≥ 2026.3.24
  • Node.js ≥ 20
  • Telegram channel configured

License

ISC

About

⚡ Instant inline keyboard menu for OpenClaw Telegram — sub-second LLM-bypass skill launcher

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors