Skip to content

Commit 98cbf7f

Browse files
committed
fix: show current think level in Telegram picker (#78278)
1 parent 1672d35 commit 98cbf7f

3 files changed

Lines changed: 159 additions & 7 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ Docs: https://docs.openclaw.ai
101101
- Plugins/catalog: add an `@tencent-weixin/openclaw-weixin` external entry pinned to `2.4.1` so onboarding and `openclaw channels add` can install the Tencent Weixin (personal WeChat) channel by default. (#77269) Thanks @pumpkinxing1.
102102
- Developer tooling: add checked-in VS Code Gateway debugging configs and an opt-in `OUTPUT_SOURCE_MAPS=1` source-map build path for breakpoints in TypeScript source. (#45710) Thanks @SwissArmyBud.
103103
- Managed proxy: add `proxy.loopbackMode` for Gateway loopback control-plane traffic, allowing operators to keep the default Gateway loopback bypass, force loopback Gateway traffic through the proxy, or block it. (#77018) Thanks @jesse-merhi.
104+
- Telegram/native commands: show the current thinking level above the `/think` level picker so users can see the active setting before changing it. (#78278) Thanks @obviyus.
104105

105106
### Fixes
106107

extensions/telegram/src/bot-native-commands.session-meta.test.ts

Lines changed: 90 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -512,12 +512,24 @@ describe("registerTelegramNativeCommands — session metadata", () => {
512512
});
513513

514514
it("uses the target session model when building native argument menus", async () => {
515-
const cfg: OpenClawConfig = {};
515+
const cfg = {
516+
agents: {
517+
defaults: {
518+
thinkingDefault: "low",
519+
models: {
520+
"anthropic/claude-opus-4-7": {
521+
params: { thinking: "xhigh" },
522+
},
523+
},
524+
},
525+
},
526+
} as OpenClawConfig;
516527
sessionMocks.loadSessionStore.mockReturnValue({
517528
"agent:main:main": {
518529
providerOverride: "anthropic",
519530
modelOverride: "claude-opus-4-7",
520531
modelOverrideSource: "user",
532+
thinkingLevel: "high",
521533
updatedAt: 0,
522534
},
523535
});
@@ -541,7 +553,7 @@ describe("registerTelegramNativeCommands — session metadata", () => {
541553
expect(sessionMocks.loadSessionStore).toHaveBeenCalledWith("/tmp/openclaw-sessions.json");
542554
expect(sendMessage).toHaveBeenCalledWith(
543555
100,
544-
expect.stringContaining("Choose level for /think."),
556+
expect.stringContaining("Current thinking level: high.\nChoose level for /think."),
545557
expect.objectContaining({ reply_markup: expect.any(Object) }),
546558
);
547559
expect(replyMocks.dispatchReplyWithBufferedBlockDispatcher).not.toHaveBeenCalled();
@@ -587,6 +599,7 @@ describe("registerTelegramNativeCommands — session metadata", () => {
587599
agents: {
588600
defaults: {
589601
model: { primary: "openai/gpt-5.5" },
602+
thinkingDefault: "medium",
590603
},
591604
},
592605
} as OpenClawConfig;
@@ -619,7 +632,81 @@ describe("registerTelegramNativeCommands — session metadata", () => {
619632
);
620633
expect(sendMessage).toHaveBeenCalledWith(
621634
100,
622-
expect.stringContaining("Choose level for /think."),
635+
expect.stringContaining("Current thinking level: medium.\nChoose level for /think."),
636+
expect.objectContaining({ reply_markup: expect.any(Object) }),
637+
);
638+
expect(replyMocks.dispatchReplyWithBufferedBlockDispatcher).not.toHaveBeenCalled();
639+
});
640+
641+
it("uses target model thinking defaults before global thinking defaults", async () => {
642+
const cfg = {
643+
agents: {
644+
defaults: {
645+
thinkingDefault: "low",
646+
models: {
647+
"anthropic/claude-opus-4-7": {
648+
params: { thinking: "xhigh" },
649+
},
650+
},
651+
},
652+
},
653+
} as OpenClawConfig;
654+
sessionMocks.loadSessionStore.mockReturnValue({
655+
"agent:main:main": {
656+
providerOverride: "anthropic",
657+
modelOverride: "claude-opus-4-7",
658+
modelOverrideSource: "user",
659+
updatedAt: 0,
660+
},
661+
});
662+
663+
const { handler, sendMessage } = registerAndResolveCommandHandler({
664+
commandName: "think",
665+
cfg,
666+
allowFrom: ["*"],
667+
});
668+
await handler(createTelegramPrivateCommandContext());
669+
670+
expect(sendMessage).toHaveBeenCalledWith(
671+
100,
672+
expect.stringContaining("Current thinking level: xhigh.\nChoose level for /think."),
673+
expect.objectContaining({ reply_markup: expect.any(Object) }),
674+
);
675+
expect(replyMocks.dispatchReplyWithBufferedBlockDispatcher).not.toHaveBeenCalled();
676+
});
677+
678+
it("uses per-agent thinking defaults before target model and global thinking defaults", async () => {
679+
const cfg = {
680+
agents: {
681+
defaults: {
682+
thinkingDefault: "low",
683+
models: {
684+
"anthropic/claude-opus-4-7": {
685+
params: { thinking: "xhigh" },
686+
},
687+
},
688+
},
689+
list: [
690+
{
691+
id: "alpha",
692+
model: { primary: "anthropic/claude-opus-4-7" },
693+
thinkingDefault: "minimal",
694+
},
695+
],
696+
},
697+
} as OpenClawConfig;
698+
sessionMocks.loadSessionStore.mockReturnValue({});
699+
700+
const { handler, sendMessage } = registerAndResolveCommandHandler({
701+
commandName: "think",
702+
cfg,
703+
allowFrom: ["*"],
704+
});
705+
await handler(createTelegramPrivateCommandContext());
706+
707+
expect(sendMessage).toHaveBeenCalledWith(
708+
100,
709+
expect.stringContaining("Current thinking level: minimal.\nChoose level for /think."),
623710
expect.objectContaining({ reply_markup: expect.any(Object) }),
624711
);
625712
expect(replyMocks.dispatchReplyWithBufferedBlockDispatcher).not.toHaveBeenCalled();

extensions/telegram/src/bot-native-commands.ts

Lines changed: 68 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
import { randomUUID } from "node:crypto";
22
import path from "node:path";
33
import type { Bot, Context } from "grammy";
4-
import { resolveDefaultModelForAgent } from "openclaw/plugin-sdk/agent-runtime";
4+
import {
5+
buildConfiguredModelCatalog,
6+
resolveAgentConfig,
7+
resolveDefaultModelForAgent,
8+
resolveThinkingDefault,
9+
} from "openclaw/plugin-sdk/agent-runtime";
510
import { resolveChannelStreamingBlockEnabled } from "openclaw/plugin-sdk/channel-streaming";
611
import {
712
resolveCommandAuthorization,
@@ -203,7 +208,7 @@ function resolveTelegramCommandMenuModelContext(params: {
203208
cfg: OpenClawConfig;
204209
agentId: string;
205210
sessionKey: string;
206-
}): { provider?: string; model?: string } {
211+
}): { provider?: string; model?: string; thinkingLevel?: string } {
207212
if (!params.sessionKey.trim()) {
208213
return {};
209214
}
@@ -215,8 +220,13 @@ function resolveTelegramCommandMenuModelContext(params: {
215220
});
216221
const store = loadSessionStore(storePath);
217222
const entry = resolveSessionStoreEntry({ store, sessionKey: params.sessionKey }).existing;
223+
const thinkingLevel = normalizeOptionalString(entry?.thinkingLevel);
218224
if (entry?.modelOverrideSource === "auto" && normalizeOptionalString(entry.modelOverride)) {
219-
return { provider: defaultModel.provider, model: defaultModel.model };
225+
return {
226+
provider: defaultModel.provider,
227+
model: defaultModel.model,
228+
...(thinkingLevel ? { thinkingLevel } : {}),
229+
};
220230
}
221231
const override = resolveStoredModelOverride({
222232
sessionEntry: entry,
@@ -228,6 +238,7 @@ function resolveTelegramCommandMenuModelContext(params: {
228238
return {
229239
provider: override.provider || defaultModel.provider,
230240
model: override.model,
241+
...(thinkingLevel ? { thinkingLevel } : {}),
231242
};
232243
}
233244
const provider =
@@ -238,12 +249,54 @@ function resolveTelegramCommandMenuModelContext(params: {
238249
return {
239250
...(provider ? { provider } : {}),
240251
...(model ? { model } : {}),
252+
...(thinkingLevel ? { thinkingLevel } : {}),
241253
};
242254
} catch {
243255
return {};
244256
}
245257
}
246258

259+
function resolveTelegramThinkMenuCurrentLevel(params: {
260+
cfg: OpenClawConfig;
261+
agentId: string;
262+
provider?: string;
263+
model?: string;
264+
thinkingLevel?: string;
265+
}): string {
266+
const explicit = normalizeOptionalString(params.thinkingLevel);
267+
if (explicit) {
268+
return explicit;
269+
}
270+
const agentThinkingDefault = normalizeOptionalString(
271+
resolveAgentConfig(params.cfg, params.agentId)?.thinkingDefault,
272+
);
273+
if (agentThinkingDefault) {
274+
return agentThinkingDefault;
275+
}
276+
const defaultModel = resolveDefaultModelForAgent({
277+
cfg: params.cfg,
278+
agentId: params.agentId,
279+
});
280+
return resolveThinkingDefault({
281+
cfg: params.cfg,
282+
provider: params.provider ?? defaultModel.provider,
283+
model: params.model ?? defaultModel.model,
284+
catalog: buildConfiguredModelCatalog({ cfg: params.cfg }),
285+
});
286+
}
287+
288+
function formatTelegramCommandArgMenuTitle(params: {
289+
command: NonNullable<ReturnType<typeof findCommandByNativeName>>;
290+
menu: NonNullable<ReturnType<typeof resolveCommandArgMenu>>;
291+
currentThinkingLevel?: string;
292+
}): string {
293+
const title = formatCommandArgMenuTitle({ command: params.command, menu: params.menu });
294+
if (params.command.key !== "think" || !params.currentThinkingLevel) {
295+
return title;
296+
}
297+
return `Current thinking level: ${params.currentThinkingLevel}.\n${title}`;
298+
}
299+
247300
function resolveTelegramNativeReplyChannelData(
248301
result: TelegramNativeReplyPayload,
249302
): TelegramNativeReplyChannelData | undefined {
@@ -1006,7 +1059,18 @@ export const registerTelegramNativeCommands = ({
10061059
})
10071060
: null;
10081061
if (menu && commandDefinition) {
1009-
const title = formatCommandArgMenuTitle({ command: commandDefinition, menu });
1062+
const title = formatTelegramCommandArgMenuTitle({
1063+
command: commandDefinition,
1064+
menu,
1065+
currentThinkingLevel:
1066+
commandDefinition.key === "think"
1067+
? resolveTelegramThinkMenuCurrentLevel({
1068+
cfg: runtimeCfg,
1069+
agentId: route.agentId,
1070+
...menuModelContext,
1071+
})
1072+
: undefined,
1073+
});
10101074
const rows: Array<Array<{ text: string; callback_data: string }>> = [];
10111075
for (let i = 0; i < menu.choices.length; i += 2) {
10121076
const slice = menu.choices.slice(i, i + 2);

0 commit comments

Comments
 (0)