talon-agent 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +137 -0
- package/bin/talon.js +5 -0
- package/package.json +86 -0
- package/prompts/base.md +13 -0
- package/prompts/custom.md.example +22 -0
- package/prompts/dream.md +41 -0
- package/prompts/identity.md +45 -0
- package/prompts/teams.md +52 -0
- package/prompts/telegram.md +89 -0
- package/prompts/terminal.md +13 -0
- package/src/__tests__/chat-id.test.ts +91 -0
- package/src/__tests__/chat-settings.test.ts +337 -0
- package/src/__tests__/config.test.ts +546 -0
- package/src/__tests__/cron-store.test.ts +440 -0
- package/src/__tests__/daily-log.test.ts +146 -0
- package/src/__tests__/dispatcher.test.ts +383 -0
- package/src/__tests__/errors.test.ts +240 -0
- package/src/__tests__/fuzz.test.ts +302 -0
- package/src/__tests__/gateway-actions.test.ts +1453 -0
- package/src/__tests__/gateway-context.test.ts +102 -0
- package/src/__tests__/gateway-http.test.ts +245 -0
- package/src/__tests__/handlers.test.ts +351 -0
- package/src/__tests__/history-persistence.test.ts +172 -0
- package/src/__tests__/history.test.ts +659 -0
- package/src/__tests__/integration.test.ts +189 -0
- package/src/__tests__/log.test.ts +110 -0
- package/src/__tests__/media-index.test.ts +277 -0
- package/src/__tests__/plugin.test.ts +317 -0
- package/src/__tests__/prompt-builder.test.ts +71 -0
- package/src/__tests__/sessions.test.ts +594 -0
- package/src/__tests__/teams-frontend.test.ts +239 -0
- package/src/__tests__/telegram.test.ts +177 -0
- package/src/__tests__/terminal-commands.test.ts +367 -0
- package/src/__tests__/terminal-frontend.test.ts +141 -0
- package/src/__tests__/terminal-renderer.test.ts +278 -0
- package/src/__tests__/watchdog.test.ts +287 -0
- package/src/__tests__/workspace.test.ts +184 -0
- package/src/backend/claude-sdk/index.ts +438 -0
- package/src/backend/claude-sdk/tools.ts +605 -0
- package/src/backend/opencode/index.ts +252 -0
- package/src/bootstrap.ts +134 -0
- package/src/cli.ts +611 -0
- package/src/core/cron.ts +148 -0
- package/src/core/dispatcher.ts +126 -0
- package/src/core/dream.ts +295 -0
- package/src/core/errors.ts +206 -0
- package/src/core/gateway-actions.ts +267 -0
- package/src/core/gateway.ts +258 -0
- package/src/core/plugin.ts +432 -0
- package/src/core/prompt-builder.ts +43 -0
- package/src/core/pulse.ts +175 -0
- package/src/core/types.ts +85 -0
- package/src/frontend/teams/actions.ts +101 -0
- package/src/frontend/teams/formatting.ts +220 -0
- package/src/frontend/teams/graph.ts +297 -0
- package/src/frontend/teams/index.ts +308 -0
- package/src/frontend/teams/proxy-fetch.ts +28 -0
- package/src/frontend/teams/tools.ts +177 -0
- package/src/frontend/telegram/actions.ts +437 -0
- package/src/frontend/telegram/admin.ts +178 -0
- package/src/frontend/telegram/callbacks.ts +251 -0
- package/src/frontend/telegram/commands.ts +543 -0
- package/src/frontend/telegram/formatting.ts +101 -0
- package/src/frontend/telegram/handlers.ts +1008 -0
- package/src/frontend/telegram/helpers.ts +105 -0
- package/src/frontend/telegram/index.ts +130 -0
- package/src/frontend/telegram/middleware.ts +177 -0
- package/src/frontend/telegram/userbot.ts +546 -0
- package/src/frontend/terminal/commands.ts +303 -0
- package/src/frontend/terminal/index.ts +282 -0
- package/src/frontend/terminal/input.ts +297 -0
- package/src/frontend/terminal/renderer.ts +248 -0
- package/src/index.ts +144 -0
- package/src/login.ts +89 -0
- package/src/storage/chat-settings.ts +218 -0
- package/src/storage/cron-store.ts +165 -0
- package/src/storage/daily-log.ts +97 -0
- package/src/storage/history.ts +278 -0
- package/src/storage/media-index.ts +116 -0
- package/src/storage/sessions.ts +328 -0
- package/src/util/chat-id.ts +21 -0
- package/src/util/config.ts +244 -0
- package/src/util/log.ts +122 -0
- package/src/util/paths.ts +80 -0
- package/src/util/time.ts +86 -0
- package/src/util/trace.ts +35 -0
- package/src/util/watchdog.ts +108 -0
- package/src/util/workspace.ts +208 -0
- package/tsconfig.json +13 -0
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Admin command handlers — /admin subcommands for bot operators.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { Bot, Context } from "grammy";
|
|
6
|
+
import { readFileSync } from "node:fs";
|
|
7
|
+
import type { TalonConfig } from "../../util/config.js";
|
|
8
|
+
import { files, dirs } from "../../util/paths.js";
|
|
9
|
+
import { escapeHtml } from "./formatting.js";
|
|
10
|
+
import {
|
|
11
|
+
resetSession,
|
|
12
|
+
getAllSessions,
|
|
13
|
+
} from "../../storage/sessions.js";
|
|
14
|
+
import { clearHistory } from "../../storage/history.js";
|
|
15
|
+
import { getChatSettings } from "../../storage/chat-settings.js";
|
|
16
|
+
import { getAllCronJobs, validateCronExpression } from "../../storage/cron-store.js";
|
|
17
|
+
import { getActiveCount } from "../../core/dispatcher.js";
|
|
18
|
+
import { getPulseStatus } from "../../core/pulse.js";
|
|
19
|
+
import { getHealthStatus, getRecentErrors } from "../../util/watchdog.js";
|
|
20
|
+
import { formatDuration } from "./helpers.js";
|
|
21
|
+
|
|
22
|
+
export async function handleAdminCommand(
|
|
23
|
+
ctx: Context,
|
|
24
|
+
bot: Bot,
|
|
25
|
+
config: TalonConfig,
|
|
26
|
+
): Promise<void> {
|
|
27
|
+
const args = (ctx.match as string ?? "").trim();
|
|
28
|
+
const [subcommand, ...rest] = args.split(/\s+/);
|
|
29
|
+
|
|
30
|
+
switch (subcommand) {
|
|
31
|
+
case "chats": {
|
|
32
|
+
const sessions = getAllSessions();
|
|
33
|
+
if (sessions.length === 0) { await ctx.reply("No active sessions."); return; }
|
|
34
|
+
sessions.sort((a, b) => (b.info.lastActive || 0) - (a.info.lastActive || 0));
|
|
35
|
+
|
|
36
|
+
const titles = new Map<string, string>();
|
|
37
|
+
await Promise.all(sessions.map(async (s) => {
|
|
38
|
+
try {
|
|
39
|
+
const id = parseInt(s.chatId, 10);
|
|
40
|
+
if (isNaN(id)) return;
|
|
41
|
+
const chat = await bot.api.getChat(id);
|
|
42
|
+
titles.set(s.chatId, "title" in chat ? (chat.title ?? "DM") : "first_name" in chat ? (chat.first_name ?? "DM") : "DM");
|
|
43
|
+
} catch { /* inaccessible */ }
|
|
44
|
+
}));
|
|
45
|
+
|
|
46
|
+
const lines = sessions.map((s) => {
|
|
47
|
+
const age = s.info.lastActive ? `${Math.round((Date.now() - s.info.lastActive) / 60000)}m ago` : "?";
|
|
48
|
+
const title = titles.get(s.chatId) ?? s.chatId;
|
|
49
|
+
const model = (getChatSettings(s.chatId).model ?? config.model).replace("claude-", "");
|
|
50
|
+
return `<b>${escapeHtml(title)}</b> <code>${s.chatId}</code>\n ${s.info.turns} turns | ${age} | ${model}`;
|
|
51
|
+
});
|
|
52
|
+
await ctx.reply(`<b>Active chats (${sessions.length})</b>\n\n` + lines.join("\n\n"), { parse_mode: "HTML" });
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
case "broadcast": {
|
|
57
|
+
const text = rest.join(" ");
|
|
58
|
+
if (!text) { await ctx.reply("Usage: /admin broadcast <text>"); return; }
|
|
59
|
+
const sessions = getAllSessions();
|
|
60
|
+
let sent = 0, failed = 0;
|
|
61
|
+
for (const s of sessions) {
|
|
62
|
+
const id = parseInt(s.chatId, 10);
|
|
63
|
+
if (isNaN(id)) continue;
|
|
64
|
+
try { await bot.api.sendMessage(id, text); sent++; } catch { failed++; }
|
|
65
|
+
}
|
|
66
|
+
await ctx.reply(`Broadcast: ${sent} sent, ${failed} failed (${sessions.length} total).`);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
case "kill": {
|
|
71
|
+
const target = rest[0];
|
|
72
|
+
if (!target) { await ctx.reply("Usage: /admin kill <chatId>"); return; }
|
|
73
|
+
resetSession(target);
|
|
74
|
+
clearHistory(target);
|
|
75
|
+
await ctx.reply(`Session ${target} reset.`);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
case "logs": {
|
|
80
|
+
const logPath = files.log;
|
|
81
|
+
try {
|
|
82
|
+
const { statSync, openSync, readSync, closeSync } = await import("node:fs");
|
|
83
|
+
const stat = statSync(logPath);
|
|
84
|
+
const size = Math.min(8192, stat.size);
|
|
85
|
+
const buf = Buffer.alloc(size);
|
|
86
|
+
const fd = openSync(logPath, "r");
|
|
87
|
+
readSync(fd, buf, 0, size, Math.max(0, stat.size - size));
|
|
88
|
+
closeSync(fd);
|
|
89
|
+
const lines = buf.toString("utf-8").trim().split("\n").slice(-20).join("\n");
|
|
90
|
+
await ctx.reply(`<pre>${escapeHtml(lines.slice(0, 3800))}</pre>`, { parse_mode: "HTML" });
|
|
91
|
+
} catch { await ctx.reply(`Could not read ${logPath}`); }
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
case "stats": {
|
|
96
|
+
const h = getHealthStatus();
|
|
97
|
+
const sessions = getAllSessions();
|
|
98
|
+
const turns = sessions.reduce((s, x) => s + x.info.turns, 0);
|
|
99
|
+
const mem = process.memoryUsage();
|
|
100
|
+
await ctx.reply([
|
|
101
|
+
`<b>\uD83E\uDD85 Talon Stats</b>`, "",
|
|
102
|
+
`<b>Uptime:</b> ${formatDuration(h.uptimeMs)}`,
|
|
103
|
+
`<b>Messages:</b> ${h.totalMessagesProcessed}`,
|
|
104
|
+
`<b>Sessions:</b> ${sessions.length}`,
|
|
105
|
+
`<b>Turns:</b> ${turns}`,
|
|
106
|
+
`<b>Last active:</b> ${h.msSinceLastMessage < 60000 ? "now" : formatDuration(h.msSinceLastMessage) + " ago"}`, "",
|
|
107
|
+
`<b>Memory:</b> ${(mem.heapUsed / 1024 / 1024).toFixed(1)}MB heap / ${(mem.rss / 1024 / 1024).toFixed(1)}MB rss`,
|
|
108
|
+
`<b>Queue:</b> ${getActiveCount()}`,
|
|
109
|
+
`<b>Errors:</b> ${h.recentErrorCount}`,
|
|
110
|
+
].join("\n"), { parse_mode: "HTML" });
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
case "errors": {
|
|
115
|
+
const errors = getRecentErrors(5);
|
|
116
|
+
if (errors.length === 0) { await ctx.reply("No recent errors."); return; }
|
|
117
|
+
const lines = errors.map((e) => `<code>[${new Date(e.timestamp).toISOString().slice(11, 19)}]</code> ${escapeHtml(e.message.slice(0, 200))}`);
|
|
118
|
+
await ctx.reply(`<b>Recent Errors (${errors.length})</b>\n\n` + lines.join("\n\n"), { parse_mode: "HTML" });
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
case "cron": {
|
|
123
|
+
const jobs = getAllCronJobs();
|
|
124
|
+
if (jobs.length === 0) { await ctx.reply("No cron jobs."); return; }
|
|
125
|
+
const lines = jobs.map((j) => {
|
|
126
|
+
const v = validateCronExpression(j.schedule, j.timezone);
|
|
127
|
+
const last = j.lastRunAt ? new Date(j.lastRunAt).toISOString().slice(0, 16).replace("T", " ") : "never";
|
|
128
|
+
const next = v.next ? new Date(v.next).toISOString().slice(0, 16).replace("T", " ") : "?";
|
|
129
|
+
return `${j.enabled ? "\u2713" : "\u2717"} <b>${escapeHtml(j.name)}</b>\n <code>${j.schedule}</code> | ${j.type} | runs: ${j.runCount} | last: ${last} | next: ${next}`;
|
|
130
|
+
});
|
|
131
|
+
await ctx.reply(`<b>Cron Jobs (${jobs.length})</b>\n\n` + lines.join("\n\n"), { parse_mode: "HTML" });
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
case "pulse": {
|
|
136
|
+
const chats = getPulseStatus();
|
|
137
|
+
if (chats.length === 0) { await ctx.reply("No pulse chats."); return; }
|
|
138
|
+
const lines = await Promise.all(chats.map(async (p) => {
|
|
139
|
+
let title = p.chatId;
|
|
140
|
+
try {
|
|
141
|
+
const id = parseInt(p.chatId, 10);
|
|
142
|
+
if (!isNaN(id)) {
|
|
143
|
+
const chat = await bot.api.getChat(id);
|
|
144
|
+
title = "title" in chat ? (chat.title ?? p.chatId) : p.chatId;
|
|
145
|
+
}
|
|
146
|
+
} catch { /* skip */ }
|
|
147
|
+
return `${p.enabled ? "\u2713" : "\u2717"} ${escapeHtml(title)}`;
|
|
148
|
+
}));
|
|
149
|
+
await ctx.reply(`<b>Pulse (${chats.length})</b>\n\n` + lines.join("\n"), { parse_mode: "HTML" });
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
case "daily": {
|
|
154
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
155
|
+
const logPath = `${dirs.logs}/${today}.md`;
|
|
156
|
+
try {
|
|
157
|
+
const content = readFileSync(logPath, "utf-8");
|
|
158
|
+
const lines = content.trim().split("\n").slice(-30).join("\n");
|
|
159
|
+
await ctx.reply(`<b>Daily log (${today})</b>\n\n<pre>${escapeHtml(lines.slice(0, 3800))}</pre>`, { parse_mode: "HTML" });
|
|
160
|
+
} catch { await ctx.reply(`No daily log for ${today}.`); }
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
default:
|
|
165
|
+
await ctx.reply([
|
|
166
|
+
"<b>/admin commands</b>", "",
|
|
167
|
+
" stats uptime, messages, memory",
|
|
168
|
+
" errors last 5 errors",
|
|
169
|
+
" chats list all active chats",
|
|
170
|
+
" daily today's interaction log",
|
|
171
|
+
" pulse pulse status per chat",
|
|
172
|
+
" cron list all cron jobs",
|
|
173
|
+
" broadcast <text> send to all chats",
|
|
174
|
+
" kill <chatId> reset a chat session",
|
|
175
|
+
" logs last 20 lines of log",
|
|
176
|
+
].join("\n"), { parse_mode: "HTML" });
|
|
177
|
+
}
|
|
178
|
+
}
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* All callback_query handlers (settings panel, model/effort selectors, proactive toggle).
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { Bot } from "grammy";
|
|
6
|
+
import type { TalonConfig } from "../../util/config.js";
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
getChatSettings,
|
|
10
|
+
setChatModel,
|
|
11
|
+
setChatEffort,
|
|
12
|
+
resolveModelName,
|
|
13
|
+
EFFORT_LEVELS,
|
|
14
|
+
type EffortLevel,
|
|
15
|
+
} from "../../storage/chat-settings.js";
|
|
16
|
+
import {
|
|
17
|
+
registerChat,
|
|
18
|
+
disablePulse,
|
|
19
|
+
enablePulse,
|
|
20
|
+
isPulseEnabled,
|
|
21
|
+
} from "../../core/pulse.js";
|
|
22
|
+
import { handleCallbackQuery } from "./handlers.js";
|
|
23
|
+
import { escapeHtml } from "./formatting.js";
|
|
24
|
+
import {
|
|
25
|
+
renderSettingsText,
|
|
26
|
+
renderSettingsKeyboard,
|
|
27
|
+
} from "./helpers.js";
|
|
28
|
+
|
|
29
|
+
export function registerCallbacks(bot: Bot, config: TalonConfig): void {
|
|
30
|
+
// ── Callback query handler ──────────────────────────────────────────────────
|
|
31
|
+
|
|
32
|
+
bot.on("callback_query:data", async (ctx) => {
|
|
33
|
+
const data = ctx.callbackQuery.data;
|
|
34
|
+
const cid = String(ctx.chat?.id ?? ctx.from.id);
|
|
35
|
+
|
|
36
|
+
// Handle unified /settings callbacks
|
|
37
|
+
if (data.startsWith("settings:")) {
|
|
38
|
+
const parts = data.split(":");
|
|
39
|
+
if (!parts[1]) {
|
|
40
|
+
await ctx.answerCallbackQuery({ text: "Invalid callback data" });
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const category = parts[1];
|
|
44
|
+
const value = parts[2] ?? "";
|
|
45
|
+
|
|
46
|
+
if (category === "done") {
|
|
47
|
+
await ctx.answerCallbackQuery({ text: "Done" });
|
|
48
|
+
try {
|
|
49
|
+
await ctx.deleteMessage();
|
|
50
|
+
} catch {
|
|
51
|
+
// might not have permission to delete
|
|
52
|
+
}
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (category === "model") {
|
|
57
|
+
if (value === "reset") {
|
|
58
|
+
setChatModel(cid, undefined);
|
|
59
|
+
} else {
|
|
60
|
+
const resolved = resolveModelName(value);
|
|
61
|
+
setChatModel(cid, resolved);
|
|
62
|
+
}
|
|
63
|
+
await ctx.answerCallbackQuery({
|
|
64
|
+
text: `Model: ${getChatSettings(cid).model ?? config.model}`,
|
|
65
|
+
});
|
|
66
|
+
} else if (category === "effort") {
|
|
67
|
+
if (value === "adaptive") {
|
|
68
|
+
setChatEffort(cid, undefined);
|
|
69
|
+
} else if (EFFORT_LEVELS.includes(value as EffortLevel)) {
|
|
70
|
+
setChatEffort(cid, value as EffortLevel);
|
|
71
|
+
}
|
|
72
|
+
await ctx.answerCallbackQuery({
|
|
73
|
+
text: `Effort: ${getChatSettings(cid).effort ?? "adaptive"}`,
|
|
74
|
+
});
|
|
75
|
+
} else if (category === "proactive") {
|
|
76
|
+
if (value === "on") {
|
|
77
|
+
enablePulse(cid);
|
|
78
|
+
registerChat(cid);
|
|
79
|
+
} else {
|
|
80
|
+
disablePulse(cid);
|
|
81
|
+
}
|
|
82
|
+
await ctx.answerCallbackQuery({ text: `Pulse: ${value}` });
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const chatSets = getChatSettings(cid);
|
|
86
|
+
const activeModel = chatSets.model ?? config.model;
|
|
87
|
+
const effortName = chatSets.effort ?? "adaptive";
|
|
88
|
+
const pulseOn = isPulseEnabled(cid);
|
|
89
|
+
|
|
90
|
+
try {
|
|
91
|
+
await ctx.editMessageText(
|
|
92
|
+
renderSettingsText(
|
|
93
|
+
activeModel,
|
|
94
|
+
effortName,
|
|
95
|
+
pulseOn,
|
|
96
|
+
chatSets.pulseIntervalMs,
|
|
97
|
+
),
|
|
98
|
+
{
|
|
99
|
+
parse_mode: "HTML",
|
|
100
|
+
reply_markup: {
|
|
101
|
+
inline_keyboard: renderSettingsKeyboard(
|
|
102
|
+
activeModel,
|
|
103
|
+
effortName,
|
|
104
|
+
pulseOn,
|
|
105
|
+
),
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
);
|
|
109
|
+
} catch {
|
|
110
|
+
/* message unchanged */
|
|
111
|
+
}
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Handle pulse callbacks
|
|
116
|
+
if (data.startsWith("pulse:")) {
|
|
117
|
+
const val = data.slice(6);
|
|
118
|
+
if (val === "on") {
|
|
119
|
+
enablePulse(cid);
|
|
120
|
+
registerChat(cid);
|
|
121
|
+
await ctx.answerCallbackQuery({ text: "Pulse: on" });
|
|
122
|
+
} else if (val === "off") {
|
|
123
|
+
disablePulse(cid);
|
|
124
|
+
await ctx.answerCallbackQuery({ text: "Pulse: off" });
|
|
125
|
+
}
|
|
126
|
+
const enabled = isPulseEnabled(cid);
|
|
127
|
+
try {
|
|
128
|
+
await ctx.editMessageText(
|
|
129
|
+
`<b>🔔 Pulse:</b> ${enabled ? "on" : "off"}\n\nReads along every few minutes and jumps in when there's something to add.`,
|
|
130
|
+
{
|
|
131
|
+
parse_mode: "HTML",
|
|
132
|
+
reply_markup: {
|
|
133
|
+
inline_keyboard: [[
|
|
134
|
+
{ text: enabled ? "✓ On" : "On", callback_data: "pulse:on" },
|
|
135
|
+
{ text: !enabled ? "✓ Off" : "Off", callback_data: "pulse:off" },
|
|
136
|
+
]],
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
);
|
|
140
|
+
} catch { /* unchanged */ }
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Handle effort callbacks
|
|
145
|
+
if (data.startsWith("effort:")) {
|
|
146
|
+
const level = data.slice(7);
|
|
147
|
+
if (level === "adaptive") {
|
|
148
|
+
setChatEffort(cid, undefined);
|
|
149
|
+
await ctx.answerCallbackQuery({ text: "Effort: adaptive" });
|
|
150
|
+
} else if (EFFORT_LEVELS.includes(level as EffortLevel)) {
|
|
151
|
+
setChatEffort(cid, level as EffortLevel);
|
|
152
|
+
await ctx.answerCallbackQuery({ text: `Effort: ${level}` });
|
|
153
|
+
}
|
|
154
|
+
const current = getChatSettings(cid).effort ?? "adaptive";
|
|
155
|
+
try {
|
|
156
|
+
await ctx.editMessageText(`<b>Effort:</b> ${current}`, {
|
|
157
|
+
parse_mode: "HTML",
|
|
158
|
+
reply_markup: {
|
|
159
|
+
inline_keyboard: [
|
|
160
|
+
[
|
|
161
|
+
{
|
|
162
|
+
text: current === "off" ? "\u2713 Off" : "Off",
|
|
163
|
+
callback_data: "effort:off",
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
text: current === "low" ? "\u2713 Low" : "Low",
|
|
167
|
+
callback_data: "effort:low",
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
text: current === "medium" ? "\u2713 Med" : "Med",
|
|
171
|
+
callback_data: "effort:medium",
|
|
172
|
+
},
|
|
173
|
+
],
|
|
174
|
+
[
|
|
175
|
+
{
|
|
176
|
+
text: current === "high" ? "\u2713 High" : "High",
|
|
177
|
+
callback_data: "effort:high",
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
text: current === "max" ? "\u2713 Max" : "Max",
|
|
181
|
+
callback_data: "effort:max",
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
text: current === "adaptive" ? "\u2713 Auto" : "Auto",
|
|
185
|
+
callback_data: "effort:adaptive",
|
|
186
|
+
},
|
|
187
|
+
],
|
|
188
|
+
],
|
|
189
|
+
},
|
|
190
|
+
});
|
|
191
|
+
} catch {
|
|
192
|
+
/* message unchanged */
|
|
193
|
+
}
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Handle model callbacks
|
|
198
|
+
if (data.startsWith("model:")) {
|
|
199
|
+
const model = data.slice(6);
|
|
200
|
+
if (model === "reset") {
|
|
201
|
+
setChatModel(cid, undefined);
|
|
202
|
+
await ctx.answerCallbackQuery({
|
|
203
|
+
text: `Model: ${config.model} (default)`,
|
|
204
|
+
});
|
|
205
|
+
} else {
|
|
206
|
+
const resolved = resolveModelName(model);
|
|
207
|
+
setChatModel(cid, resolved);
|
|
208
|
+
await ctx.answerCallbackQuery({
|
|
209
|
+
text: `Model: ${resolved}`,
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
const current = getChatSettings(cid).model ?? config.model;
|
|
213
|
+
const isModel = (id: string) => current.includes(id);
|
|
214
|
+
try {
|
|
215
|
+
await ctx.editMessageText(
|
|
216
|
+
`<b>Model:</b> <code>${escapeHtml(current)}</code>`,
|
|
217
|
+
{
|
|
218
|
+
parse_mode: "HTML",
|
|
219
|
+
reply_markup: {
|
|
220
|
+
inline_keyboard: [
|
|
221
|
+
[
|
|
222
|
+
{
|
|
223
|
+
text: isModel("sonnet") ? "\u2713 Sonnet 4.6" : "Sonnet 4.6",
|
|
224
|
+
callback_data: "model:sonnet",
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
text: isModel("opus") ? "\u2713 Opus 4.6" : "Opus 4.6",
|
|
228
|
+
callback_data: "model:opus",
|
|
229
|
+
},
|
|
230
|
+
],
|
|
231
|
+
[
|
|
232
|
+
{
|
|
233
|
+
text: isModel("haiku") ? "\u2713 Haiku 4.5" : "Haiku 4.5",
|
|
234
|
+
callback_data: "model:haiku",
|
|
235
|
+
},
|
|
236
|
+
{ text: "Reset to default", callback_data: "model:reset" },
|
|
237
|
+
],
|
|
238
|
+
],
|
|
239
|
+
},
|
|
240
|
+
},
|
|
241
|
+
);
|
|
242
|
+
} catch {
|
|
243
|
+
/* message unchanged */
|
|
244
|
+
}
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Forward other callbacks to Claude
|
|
249
|
+
handleCallbackQuery(ctx, bot, config);
|
|
250
|
+
});
|
|
251
|
+
}
|