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/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.
|
- 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.
|
- 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.
|
- 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
|
## 2026.3.12
|
||||||
|
|||||||
@ -172,6 +172,22 @@ describe("runPreparedReply media-only handling", () => {
|
|||||||
expect(call?.followupRun.prompt).toContain("[User sent media without caption]");
|
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 () => {
|
it("keeps thread history context on follow-up turns", async () => {
|
||||||
const result = await runPreparedReply(
|
const result = await runPreparedReply(
|
||||||
baseParams({
|
baseParams({
|
||||||
|
|||||||
@ -43,7 +43,10 @@ import {
|
|||||||
type NormalizedAllowFrom,
|
type NormalizedAllowFrom,
|
||||||
} from "./bot-access.js";
|
} from "./bot-access.js";
|
||||||
import type { TelegramMediaRef } from "./bot-message-context.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 {
|
import {
|
||||||
MEDIA_GROUP_TIMEOUT_MS,
|
MEDIA_GROUP_TIMEOUT_MS,
|
||||||
type MediaGroupEntry,
|
type MediaGroupEntry,
|
||||||
@ -1437,14 +1440,16 @@ export const registerTelegramHandlers = ({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const nativeCommandText = parseTelegramNativeCommandCallbackData(data);
|
||||||
const syntheticMessage = buildSyntheticTextMessage({
|
const syntheticMessage = buildSyntheticTextMessage({
|
||||||
base: callbackMessage,
|
base: callbackMessage,
|
||||||
from: callback.from,
|
from: callback.from,
|
||||||
text: data,
|
text: nativeCommandText ?? data,
|
||||||
});
|
});
|
||||||
await processMessage(buildSyntheticContext(ctx, syntheticMessage), [], storeAllowFrom, {
|
await processMessage(buildSyntheticContext(ctx, syntheticMessage), [], storeAllowFrom, {
|
||||||
forceWasMentioned: true,
|
forceWasMentioned: true,
|
||||||
messageIdOverride: callback.id,
|
messageIdOverride: callback.id,
|
||||||
|
commandSource: nativeCommandText ? "native" : undefined,
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
runtime.error?.(danger(`callback handler failed: ${String(err)}`));
|
runtime.error?.(danger(`callback handler failed: ${String(err)}`));
|
||||||
|
|||||||
@ -240,6 +240,7 @@ export async function buildTelegramInboundContextPayload(params: {
|
|||||||
StickerMediaIncluded: allMedia[0]?.stickerMetadata ? !stickerCacheHit : undefined,
|
StickerMediaIncluded: allMedia[0]?.stickerMetadata ? !stickerCacheHit : undefined,
|
||||||
...(locationData ? toLocationContext(locationData) : undefined),
|
...(locationData ? toLocationContext(locationData) : undefined),
|
||||||
CommandAuthorized: commandAuthorized,
|
CommandAuthorized: commandAuthorized,
|
||||||
|
CommandSource: options?.commandSource,
|
||||||
MessageThreadId: threadSpec.id,
|
MessageThreadId: threadSpec.id,
|
||||||
IsForum: isForum,
|
IsForum: isForum,
|
||||||
OriginatingChannel: "telegram" as const,
|
OriginatingChannel: "telegram" as const,
|
||||||
|
|||||||
@ -18,6 +18,7 @@ export type TelegramMediaRef = {
|
|||||||
export type TelegramMessageContextOptions = {
|
export type TelegramMessageContextOptions = {
|
||||||
forceWasMentioned?: boolean;
|
forceWasMentioned?: boolean;
|
||||||
messageIdOverride?: string;
|
messageIdOverride?: string;
|
||||||
|
commandSource?: "text" | "native";
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TelegramLogger = {
|
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 { TELEGRAM_COMMAND_NAME_PATTERN } from "../config/telegram-custom-commands.js";
|
||||||
import type { TelegramAccountConfig } from "../config/types.js";
|
import type { TelegramAccountConfig } from "../config/types.js";
|
||||||
import type { RuntimeEnv } from "../runtime.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";
|
import { createNativeCommandTestParams } from "./bot-native-commands.test-helpers.js";
|
||||||
|
|
||||||
const { listSkillCommandsForAgents } = vi.hoisted(() => ({
|
const { listSkillCommandsForAgents } = vi.hoisted(() => ({
|
||||||
@ -213,6 +216,55 @@ describe("registerTelegramNativeCommands", () => {
|
|||||||
expect(registeredCommands.some((entry) => entry.command === "custom-bad")).toBe(false);
|
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 () => {
|
it("passes agent-scoped media roots for plugin command replies with media", async () => {
|
||||||
const commandHandlers = new Map<string, (ctx: unknown) => Promise<void>>();
|
const commandHandlers = new Map<string, (ctx: unknown) => Promise<void>>();
|
||||||
const sendMessage = vi.fn().mockResolvedValue(undefined);
|
const sendMessage = vi.fn().mockResolvedValue(undefined);
|
||||||
|
|||||||
@ -74,6 +74,7 @@ import { resolveTelegramGroupPromptSettings } from "./group-config-helpers.js";
|
|||||||
import { buildInlineKeyboard } from "./send.js";
|
import { buildInlineKeyboard } from "./send.js";
|
||||||
|
|
||||||
const EMPTY_RESPONSE_FALLBACK = "No response generated. Please try again.";
|
const EMPTY_RESPONSE_FALLBACK = "No response generated. Please try again.";
|
||||||
|
const TELEGRAM_NATIVE_COMMAND_CALLBACK_PREFIX = "tgcmd:";
|
||||||
|
|
||||||
type TelegramNativeCommandContext = Context & { match?: string };
|
type TelegramNativeCommandContext = Context & { match?: string };
|
||||||
|
|
||||||
@ -142,6 +143,18 @@ type RegisterTelegramNativeCommandsParams = {
|
|||||||
opts: { token: string };
|
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: {
|
async function resolveTelegramCommandAuth(params: {
|
||||||
msg: NonNullable<TelegramNativeCommandContext["message"]>;
|
msg: NonNullable<TelegramNativeCommandContext["message"]>;
|
||||||
bot: Bot;
|
bot: Bot;
|
||||||
@ -632,7 +645,9 @@ export const registerTelegramNativeCommands = ({
|
|||||||
};
|
};
|
||||||
return {
|
return {
|
||||||
text: choice.label,
|
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(payload.Body).toContain("cmd:option_a");
|
||||||
expect(answerCallbackQuerySpy).toHaveBeenCalledWith("cbq-1");
|
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 () => {
|
it("wraps inbound message with Telegram envelope", async () => {
|
||||||
await withEnvAsync({ TZ: "Europe/Vienna" }, async () => {
|
await withEnvAsync({ TZ: "Europe/Vienna" }, async () => {
|
||||||
createTelegramBot({ token: "tok" });
|
createTelegramBot({ token: "tok" });
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user