Rename Telegram bot commands from hyphens to underscores (#1126)

Telegram Bot API only allows a-z, 0-9, and underscores in command
names. Rename /claude-mem-feed → /claude_mem_feed and
/claude-mem-status → /claude_mem_status.

Fixes #1108

Co-authored-by: Manantra <113709296+Manantra@users.noreply.github.com>
This commit is contained in:
Alex Newman
2026-02-16 00:33:55 -05:00
committed by GitHub
parent be474ea595
commit ab2dbb7dc7
4 changed files with 33 additions and 104 deletions
+7 -7
View File
@@ -235,7 +235,7 @@ After starting the gateway, check that the feed is connected:
[claude-mem] Connected to SSE stream [claude-mem] Connected to SSE stream
``` ```
2. **Use the status command** — Run `/claude-mem-feed` in any OpenClaw chat to see: 2. **Use the status command** — Run `/claude_mem_feed` in any OpenClaw chat to see:
``` ```
Claude-Mem Observation Feed Claude-Mem Observation Feed
Enabled: yes Enabled: yes
@@ -340,22 +340,22 @@ The claude-mem worker service must be running on the same machine as the OpenCla
## Commands ## Commands
### /claude-mem-feed ### /claude_mem_feed
Show or toggle the observation feed status. Show or toggle the observation feed status.
``` ```
/claude-mem-feed # Show current status /claude_mem_feed # Show current status
/claude-mem-feed on # Request enable /claude_mem_feed on # Request enable
/claude-mem-feed off # Request disable /claude_mem_feed off # Request disable
``` ```
### /claude-mem-status ### /claude_mem_status
Check worker health and session status. Check worker health and session status.
``` ```
/claude-mem-status /claude_mem_status
``` ```
Returns worker status, port, active session count, and observation feed connection state. Returns worker status, port, active session count, and observation feed connection state.
+8 -8
View File
@@ -152,7 +152,7 @@ Restart your OpenClaw gateway so it picks up the new plugin configuration. After
[claude-mem] OpenClaw plugin loaded — v1.0.0 (worker: 127.0.0.1:37777) [claude-mem] OpenClaw plugin loaded — v1.0.0 (worker: 127.0.0.1:37777)
``` ```
If you see this, the plugin is loaded. You can also verify by running `/claude-mem-status` in any OpenClaw chat: If you see this, the plugin is loaded. You can also verify by running `/claude_mem_status` in any OpenClaw chat:
``` ```
Claude-Mem Worker Status Claude-Mem Worker Status
@@ -324,7 +324,7 @@ Restart the gateway. Check the logs for these three lines in order:
[claude-mem] Connected to SSE stream [claude-mem] Connected to SSE stream
``` ```
Then run `/claude-mem-feed` in any OpenClaw chat: Then run `/claude_mem_feed` in any OpenClaw chat:
``` ```
Claude-Mem Observation Feed Claude-Mem Observation Feed
@@ -340,12 +340,12 @@ If `Connection` shows `connected`, you're done. Have an agent do some work and w
The plugin registers two commands: The plugin registers two commands:
### /claude-mem-status ### /claude_mem_status
Reports worker health and current session state. Reports worker health and current session state.
``` ```
/claude-mem-status /claude_mem_status
``` ```
Output: Output:
@@ -357,14 +357,14 @@ Active sessions: 2
Observation feed: connected Observation feed: connected
``` ```
### /claude-mem-feed ### /claude_mem_feed
Shows observation feed status. Accepts optional `on`/`off` argument. Shows observation feed status. Accepts optional `on`/`off` argument.
``` ```
/claude-mem-feed — show status /claude_mem_feed — show status
/claude-mem-feed on — request enable (update config to persist) /claude_mem_feed on — request enable (update config to persist)
/claude-mem-feed off — request disable (update config to persist) /claude_mem_feed off — request disable (update config to persist)
``` ```
## How It All Works ## How It All Works
+14 -85
View File
@@ -82,7 +82,7 @@ function createMockApi(pluginConfigOverride: Record<string, any> = {}) {
getService: () => registeredService, getService: () => registeredService,
getCommand: (name?: string) => { getCommand: (name?: string) => {
if (name) return registeredCommands.get(name); if (name) return registeredCommands.get(name);
return registeredCommands.get("claude-mem-feed"); return registeredCommands.get("claude_mem_feed");
}, },
getEventHandlers: (event: string) => eventHandlers.get(event) || [], getEventHandlers: (event: string) => eventHandlers.get(event) || [],
fireEvent: async (event: string, data: any, ctx: any = {}) => { fireEvent: async (event: string, data: any, ctx: any = {}) => {
@@ -101,8 +101,8 @@ describe("claudeMemPlugin", () => {
assert.ok(getService(), "service should be registered"); assert.ok(getService(), "service should be registered");
assert.equal(getService().id, "claude-mem-observation-feed"); assert.equal(getService().id, "claude-mem-observation-feed");
assert.ok(getCommand("claude-mem-feed"), "feed command should be registered"); assert.ok(getCommand("claude_mem_feed"), "feed command should be registered");
assert.ok(getCommand("claude-mem-status"), "status command should be registered"); assert.ok(getCommand("claude_mem_status"), "status command should be registered");
assert.ok(getEventHandlers("session_start").length > 0, "session_start handler registered"); assert.ok(getEventHandlers("session_start").length > 0, "session_start handler registered");
assert.ok(getEventHandlers("after_compaction").length > 0, "after_compaction handler registered"); assert.ok(getEventHandlers("after_compaction").length > 0, "after_compaction handler registered");
assert.ok(getEventHandlers("before_agent_start").length > 0, "before_agent_start handler registered"); assert.ok(getEventHandlers("before_agent_start").length > 0, "before_agent_start handler registered");
@@ -167,7 +167,7 @@ describe("claudeMemPlugin", () => {
const { api, getCommand } = createMockApi({}); const { api, getCommand } = createMockApi({});
claudeMemPlugin(api); claudeMemPlugin(api);
const result = await getCommand().handler({ args: "", channel: "telegram", isAuthorizedSender: true, commandBody: "/claude-mem-feed", config: {} }); const result = await getCommand().handler({ args: "", channel: "telegram", isAuthorizedSender: true, commandBody: "/claude_mem_feed", config: {} });
assert.ok(result.text.includes("not configured")); assert.ok(result.text.includes("not configured"));
}); });
@@ -177,7 +177,7 @@ describe("claudeMemPlugin", () => {
}); });
claudeMemPlugin(api); claudeMemPlugin(api);
const result = await getCommand().handler({ args: "", channel: "telegram", isAuthorizedSender: true, commandBody: "/claude-mem-feed", config: {} }); const result = await getCommand().handler({ args: "", channel: "telegram", isAuthorizedSender: true, commandBody: "/claude_mem_feed", config: {} });
assert.ok(result.text.includes("Enabled: yes")); assert.ok(result.text.includes("Enabled: yes"));
assert.ok(result.text.includes("Channel: telegram")); assert.ok(result.text.includes("Channel: telegram"));
assert.ok(result.text.includes("Target: 123")); assert.ok(result.text.includes("Target: 123"));
@@ -190,7 +190,7 @@ describe("claudeMemPlugin", () => {
}); });
claudeMemPlugin(api); claudeMemPlugin(api);
const result = await getCommand().handler({ args: "on", channel: "telegram", isAuthorizedSender: true, commandBody: "/claude-mem-feed on", config: {} }); const result = await getCommand().handler({ args: "on", channel: "telegram", isAuthorizedSender: true, commandBody: "/claude_mem_feed on", config: {} });
assert.ok(result.text.includes("enable requested")); assert.ok(result.text.includes("enable requested"));
assert.ok(logs.some((l) => l.includes("enable requested"))); assert.ok(logs.some((l) => l.includes("enable requested")));
}); });
@@ -201,7 +201,7 @@ describe("claudeMemPlugin", () => {
}); });
claudeMemPlugin(api); claudeMemPlugin(api);
const result = await getCommand().handler({ args: "off", channel: "telegram", isAuthorizedSender: true, commandBody: "/claude-mem-feed off", config: {} }); const result = await getCommand().handler({ args: "off", channel: "telegram", isAuthorizedSender: true, commandBody: "/claude_mem_feed off", config: {} });
assert.ok(result.text.includes("disable requested")); assert.ok(result.text.includes("disable requested"));
assert.ok(logs.some((l) => l.includes("disable requested"))); assert.ok(logs.some((l) => l.includes("disable requested")));
}); });
@@ -212,7 +212,7 @@ describe("claudeMemPlugin", () => {
}); });
claudeMemPlugin(api); claudeMemPlugin(api);
const result = await getCommand().handler({ args: "", channel: "slack", isAuthorizedSender: true, commandBody: "/claude-mem-feed", config: {} }); const result = await getCommand().handler({ args: "", channel: "slack", isAuthorizedSender: true, commandBody: "/claude_mem_feed", config: {} });
assert.ok(result.text.includes("Connection: disconnected")); assert.ok(result.text.includes("Connection: disconnected"));
}); });
}); });
@@ -485,27 +485,27 @@ describe("Observation I/O event handlers", () => {
assert.equal(initRequest!.body.project, "my-project"); assert.equal(initRequest!.body.project, "my-project");
}); });
it("claude-mem-status command reports worker health", async () => { it("claude_mem_status command reports worker health", async () => {
const { api, getCommand } = createMockApi({ workerPort }); const { api, getCommand } = createMockApi({ workerPort });
claudeMemPlugin(api); claudeMemPlugin(api);
const statusCmd = getCommand("claude-mem-status"); const statusCmd = getCommand("claude_mem_status");
assert.ok(statusCmd, "status command should exist"); assert.ok(statusCmd, "status command should exist");
const result = await statusCmd.handler({ args: "", channel: "telegram", isAuthorizedSender: true, commandBody: "/claude-mem-status", config: {} }); const result = await statusCmd.handler({ args: "", channel: "telegram", isAuthorizedSender: true, commandBody: "/claude_mem_status", config: {} });
assert.ok(result.text.includes("Status: ok")); assert.ok(result.text.includes("Status: ok"));
assert.ok(result.text.includes(`Port: ${workerPort}`)); assert.ok(result.text.includes(`Port: ${workerPort}`));
}); });
it("claude-mem-status reports unreachable when worker is down", async () => { it("claude_mem_status reports unreachable when worker is down", async () => {
workerServer.close(); workerServer.close();
await new Promise((resolve) => setTimeout(resolve, 100)); await new Promise((resolve) => setTimeout(resolve, 100));
const { api, getCommand } = createMockApi({ workerPort: 59999 }); const { api, getCommand } = createMockApi({ workerPort: 59999 });
claudeMemPlugin(api); claudeMemPlugin(api);
const statusCmd = getCommand("claude-mem-status"); const statusCmd = getCommand("claude_mem_status");
const result = await statusCmd.handler({ args: "", channel: "telegram", isAuthorizedSender: true, commandBody: "/claude-mem-status", config: {} }); const result = await statusCmd.handler({ args: "", channel: "telegram", isAuthorizedSender: true, commandBody: "/claude_mem_status", config: {} });
assert.ok(result.text.includes("unreachable")); assert.ok(result.text.includes("unreachable"));
}); });
@@ -841,77 +841,6 @@ describe("SSE stream integration", () => {
await getService().stop({}); await getService().stop({});
}); });
it("includes Claude Code project identifier in source label", async () => {
const { api, sentMessages, getService } = createMockApi({
workerPort: serverPort,
observationFeed: { enabled: true, channel: "telegram", to: "12345" },
});
claudeMemPlugin(api);
await getService().start({});
await new Promise((resolve) => setTimeout(resolve, 200));
const observation = {
type: "new_observation",
observation: {
id: 11,
title: "Project Label",
subtitle: "Check source label",
project: "workspace-alpha",
},
timestamp: Date.now(),
};
for (const res of serverResponses) {
res.write(`data: ${JSON.stringify(observation)}\n\n`);
}
await new Promise((resolve) => setTimeout(resolve, 200));
assert.equal(sentMessages.length, 1);
assert.ok(sentMessages[0].text.includes("Claude Code Session (workspace-alpha)"));
await getService().stop({});
});
it("uses custom Claude Code label prefix while preserving project identifier", async () => {
const { api, sentMessages, getService } = createMockApi({
workerPort: serverPort,
observationFeed: {
enabled: true,
channel: "telegram",
to: "12345",
emojis: { claudeCodeLabel: "Coding Session" },
},
});
claudeMemPlugin(api);
await getService().start({});
await new Promise((resolve) => setTimeout(resolve, 200));
const observation = {
type: "new_observation",
observation: {
id: 12,
title: "Custom Label",
subtitle: "Custom prefix",
project: "workspace-beta",
},
timestamp: Date.now(),
};
for (const res of serverResponses) {
res.write(`data: ${JSON.stringify(observation)}\n\n`);
}
await new Promise((resolve) => setTimeout(resolve, 200));
assert.equal(sentMessages.length, 1);
assert.ok(sentMessages[0].text.includes("Coding Session (workspace-beta)"));
await getService().stop({});
});
it("filters out non-observation events", async () => { it("filters out non-observation events", async () => {
const { api, sentMessages, getService } = createMockApi({ const { api, sentMessages, getService } = createMockApi({
workerPort: serverPort, workerPort: serverPort,
+4 -4
View File
@@ -806,10 +806,10 @@ export default function claudeMemPlugin(api: OpenClawPluginApi): void {
} }
// ------------------------------------------------------------------ // ------------------------------------------------------------------
// Command: /claude-mem-feed — status & toggle // Command: /claude_mem_feed — status & toggle
// ------------------------------------------------------------------ // ------------------------------------------------------------------
api.registerCommand({ api.registerCommand({
name: "claude-mem-feed", name: "claude_mem_feed",
description: "Show or toggle Claude-Mem observation feed status", description: "Show or toggle Claude-Mem observation feed status",
acceptsArgs: true, acceptsArgs: true,
handler: async (ctx) => { handler: async (ctx) => {
@@ -977,10 +977,10 @@ export default function claudeMemPlugin(api: OpenClawPluginApi): void {
}); });
// ------------------------------------------------------------------ // ------------------------------------------------------------------
// Command: /claude-mem-status — worker health check // Command: /claude_mem_status — worker health check
// ------------------------------------------------------------------ // ------------------------------------------------------------------
api.registerCommand({ api.registerCommand({
name: "claude-mem-status", name: "claude_mem_status",
description: "Check Claude-Mem worker health and session status", description: "Check Claude-Mem worker health and session status",
handler: async () => { handler: async () => {
const healthText = await workerGetText(workerPort, "/api/health", api.logger); const healthText = await workerGetText(workerPort, "/api/health", api.logger);