Compare commits
11 Commits
main
...
vincentkoc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f1687cd44b | ||
|
|
79a6188f5f | ||
|
|
5386d50840 | ||
|
|
38bfa88a34 | ||
|
|
29b16c5d69 | ||
|
|
daa62d8d2d | ||
|
|
40c9cb561f | ||
|
|
55628a0233 | ||
|
|
a057f2f03a | ||
|
|
2c8754c3e1 | ||
|
|
4fd0d897dc |
@ -40,6 +40,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Agents/compaction: preserve safeguard compaction summary language continuity via default and configurable custom instructions so persona drift is reduced after auto-compaction. (#10456) Thanks @keepitmello.
|
||||
- Agents/tool warnings: distinguish gated core tools like `apply_patch` from plugin-only unknown entries in `tools.profile` warnings, so unavailable core tools now report current runtime/provider/model/config gating instead of suggesting a missing plugin.
|
||||
- Slack/probe: keep `auth.test()` bot and team metadata mapping stable while simplifying the probe result path. (#44775) Thanks @Cafexss.
|
||||
- Telegram/native commands: preserve native command routing for `/fast` option-button callbacks even when text commands are disabled, so `/fast status|on|off` no longer falls through into normal model runs. Thanks @vincentkoc.
|
||||
- Dashboard/chat UI: restore the `chat-new-messages` class on the New messages scroll pill so the button uses its existing compact styling instead of rendering as a full-screen SVG overlay. (#44856) Thanks @Astro-Han.
|
||||
|
||||
## 2026.3.12
|
||||
|
||||
@ -172,6 +172,22 @@ describe("runPreparedReply media-only handling", () => {
|
||||
expect(call?.followupRun.prompt).toContain("[User sent media without caption]");
|
||||
});
|
||||
|
||||
it("passes Anthropic fast-mode session state into runReplyAgent", async () => {
|
||||
await runPreparedReply(
|
||||
baseParams({
|
||||
sessionEntry: {
|
||||
fastMode: true,
|
||||
} as never,
|
||||
}),
|
||||
);
|
||||
|
||||
const call = vi.mocked(runReplyAgent).mock.calls[0]?.[0];
|
||||
expect(call).toBeTruthy();
|
||||
expect(call?.followupRun.run.fastMode).toBe(true);
|
||||
expect(call?.followupRun.run.provider).toBe("anthropic");
|
||||
expect(call?.followupRun.run.model).toBe("claude-opus-4-1");
|
||||
});
|
||||
|
||||
it("keeps thread history context on follow-up turns", async () => {
|
||||
const result = await runPreparedReply(
|
||||
baseParams({
|
||||
|
||||
@ -43,7 +43,10 @@ import {
|
||||
type NormalizedAllowFrom,
|
||||
} from "./bot-access.js";
|
||||
import type { TelegramMediaRef } from "./bot-message-context.js";
|
||||
import { RegisterTelegramHandlerParams } from "./bot-native-commands.js";
|
||||
import {
|
||||
parseTelegramNativeCommandCallbackData,
|
||||
RegisterTelegramHandlerParams,
|
||||
} from "./bot-native-commands.js";
|
||||
import {
|
||||
MEDIA_GROUP_TIMEOUT_MS,
|
||||
type MediaGroupEntry,
|
||||
@ -1437,14 +1440,16 @@ export const registerTelegramHandlers = ({
|
||||
return;
|
||||
}
|
||||
|
||||
const nativeCommandText = parseTelegramNativeCommandCallbackData(data);
|
||||
const syntheticMessage = buildSyntheticTextMessage({
|
||||
base: callbackMessage,
|
||||
from: callback.from,
|
||||
text: data,
|
||||
text: nativeCommandText ?? data,
|
||||
});
|
||||
await processMessage(buildSyntheticContext(ctx, syntheticMessage), [], storeAllowFrom, {
|
||||
forceWasMentioned: true,
|
||||
messageIdOverride: callback.id,
|
||||
commandSource: nativeCommandText ? "native" : undefined,
|
||||
});
|
||||
} catch (err) {
|
||||
runtime.error?.(danger(`callback handler failed: ${String(err)}`));
|
||||
|
||||
@ -240,6 +240,7 @@ export async function buildTelegramInboundContextPayload(params: {
|
||||
StickerMediaIncluded: allMedia[0]?.stickerMetadata ? !stickerCacheHit : undefined,
|
||||
...(locationData ? toLocationContext(locationData) : undefined),
|
||||
CommandAuthorized: commandAuthorized,
|
||||
CommandSource: options?.commandSource,
|
||||
MessageThreadId: threadSpec.id,
|
||||
IsForum: isForum,
|
||||
OriginatingChannel: "telegram" as const,
|
||||
|
||||
@ -18,6 +18,7 @@ export type TelegramMediaRef = {
|
||||
export type TelegramMessageContextOptions = {
|
||||
forceWasMentioned?: boolean;
|
||||
messageIdOverride?: string;
|
||||
commandSource?: "text" | "native";
|
||||
};
|
||||
|
||||
export type TelegramLogger = {
|
||||
|
||||
@ -5,7 +5,10 @@ import { STATE_DIR } from "../config/paths.js";
|
||||
import { TELEGRAM_COMMAND_NAME_PATTERN } from "../config/telegram-custom-commands.js";
|
||||
import type { TelegramAccountConfig } from "../config/types.js";
|
||||
import type { RuntimeEnv } from "../runtime.js";
|
||||
import { registerTelegramNativeCommands } from "./bot-native-commands.js";
|
||||
import {
|
||||
buildTelegramNativeCommandCallbackData,
|
||||
registerTelegramNativeCommands,
|
||||
} from "./bot-native-commands.js";
|
||||
import { createNativeCommandTestParams } from "./bot-native-commands.test-helpers.js";
|
||||
|
||||
const { listSkillCommandsForAgents } = vi.hoisted(() => ({
|
||||
@ -213,6 +216,55 @@ describe("registerTelegramNativeCommands", () => {
|
||||
expect(registeredCommands.some((entry) => entry.command === "custom-bad")).toBe(false);
|
||||
});
|
||||
|
||||
it("prefixes native command menu callbacks so Telegram callback routing preserves native mode", async () => {
|
||||
const commandHandlers = new Map<string, (ctx: unknown) => Promise<void>>();
|
||||
const sendMessage = vi.fn().mockResolvedValue(undefined);
|
||||
|
||||
registerTelegramNativeCommands({
|
||||
...buildParams({}),
|
||||
bot: {
|
||||
api: {
|
||||
setMyCommands: vi.fn().mockResolvedValue(undefined),
|
||||
sendMessage,
|
||||
},
|
||||
command: vi.fn((name: string, handler: (ctx: unknown) => Promise<void>) => {
|
||||
commandHandlers.set(name, handler);
|
||||
}),
|
||||
} as unknown as Parameters<typeof registerTelegramNativeCommands>[0]["bot"],
|
||||
allowFrom: ["*"],
|
||||
});
|
||||
|
||||
const fastHandler = commandHandlers.get("fast");
|
||||
expect(fastHandler).toBeDefined();
|
||||
|
||||
await fastHandler?.({
|
||||
message: {
|
||||
chat: { id: 1234, type: "private" },
|
||||
date: 1736380800,
|
||||
from: { id: 9, first_name: "Ada", username: "ada_bot" },
|
||||
message_id: 44,
|
||||
},
|
||||
me: { username: "openclaw_bot" },
|
||||
match: "",
|
||||
});
|
||||
|
||||
expect(sendMessage).toHaveBeenCalledTimes(1);
|
||||
const [, , params] = sendMessage.mock.calls[0] ?? [];
|
||||
expect(params).toEqual(
|
||||
expect.objectContaining({
|
||||
reply_markup: expect.objectContaining({
|
||||
inline_keyboard: expect.arrayContaining([
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
callback_data: buildTelegramNativeCommandCallbackData("/fast status"),
|
||||
}),
|
||||
]),
|
||||
]),
|
||||
}),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("passes agent-scoped media roots for plugin command replies with media", async () => {
|
||||
const commandHandlers = new Map<string, (ctx: unknown) => Promise<void>>();
|
||||
const sendMessage = vi.fn().mockResolvedValue(undefined);
|
||||
|
||||
@ -74,6 +74,7 @@ import { resolveTelegramGroupPromptSettings } from "./group-config-helpers.js";
|
||||
import { buildInlineKeyboard } from "./send.js";
|
||||
|
||||
const EMPTY_RESPONSE_FALLBACK = "No response generated. Please try again.";
|
||||
const TELEGRAM_NATIVE_COMMAND_CALLBACK_PREFIX = "tgcmd:";
|
||||
|
||||
type TelegramNativeCommandContext = Context & { match?: string };
|
||||
|
||||
@ -142,6 +143,18 @@ type RegisterTelegramNativeCommandsParams = {
|
||||
opts: { token: string };
|
||||
};
|
||||
|
||||
export function buildTelegramNativeCommandCallbackData(commandText: string): string {
|
||||
return `${TELEGRAM_NATIVE_COMMAND_CALLBACK_PREFIX}${commandText}`;
|
||||
}
|
||||
|
||||
export function parseTelegramNativeCommandCallbackData(data: string): string | null {
|
||||
if (!data.startsWith(TELEGRAM_NATIVE_COMMAND_CALLBACK_PREFIX)) {
|
||||
return null;
|
||||
}
|
||||
const commandText = data.slice(TELEGRAM_NATIVE_COMMAND_CALLBACK_PREFIX.length).trim();
|
||||
return commandText || null;
|
||||
}
|
||||
|
||||
async function resolveTelegramCommandAuth(params: {
|
||||
msg: NonNullable<TelegramNativeCommandContext["message"]>;
|
||||
bot: Bot;
|
||||
@ -632,7 +645,9 @@ export const registerTelegramNativeCommands = ({
|
||||
};
|
||||
return {
|
||||
text: choice.label,
|
||||
callback_data: buildCommandTextFromArgs(commandDefinition, args),
|
||||
callback_data: buildTelegramNativeCommandCallbackData(
|
||||
buildCommandTextFromArgs(commandDefinition, args),
|
||||
),
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
||||
@ -143,6 +143,41 @@ describe("createTelegramBot", () => {
|
||||
expect(payload.Body).toContain("cmd:option_a");
|
||||
expect(answerCallbackQuerySpy).toHaveBeenCalledWith("cbq-1");
|
||||
});
|
||||
|
||||
it("preserves native command source for Telegram command-menu callbacks when text commands are disabled", async () => {
|
||||
loadConfig.mockReturnValue({
|
||||
commands: { text: false },
|
||||
channels: {
|
||||
telegram: { dmPolicy: "open", allowFrom: ["*"] },
|
||||
},
|
||||
});
|
||||
createTelegramBot({ token: "tok" });
|
||||
const callbackHandler = onSpy.mock.calls.find((call) => call[0] === "callback_query")?.[1] as (
|
||||
ctx: Record<string, unknown>,
|
||||
) => Promise<void>;
|
||||
expect(callbackHandler).toBeDefined();
|
||||
|
||||
await callbackHandler({
|
||||
callbackQuery: {
|
||||
id: "cbq-native-fast",
|
||||
data: "tgcmd:/fast status",
|
||||
from: { id: 9, first_name: "Ada", username: "ada_bot" },
|
||||
message: {
|
||||
chat: { id: 1234, type: "private" },
|
||||
date: 1736380800,
|
||||
message_id: 10,
|
||||
},
|
||||
},
|
||||
me: { username: "openclaw_bot" },
|
||||
getFile: async () => ({ download: async () => new Uint8Array() }),
|
||||
});
|
||||
|
||||
expect(replySpy).toHaveBeenCalledTimes(1);
|
||||
const payload = replySpy.mock.calls[0][0];
|
||||
expect(payload.CommandBody).toBe("/fast status");
|
||||
expect(payload.CommandSource).toBe("native");
|
||||
expect(answerCallbackQuerySpy).toHaveBeenCalledWith("cbq-native-fast");
|
||||
});
|
||||
it("wraps inbound message with Telegram envelope", async () => {
|
||||
await withEnvAsync({ TZ: "Europe/Vienna" }, async () => {
|
||||
createTelegramBot({ token: "tok" });
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user