moltbot-termux 2026.1.27-1 → 2026.1.27-2-pre
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/CHANGELOG.md +8 -0
- package/dist/agents/channel-tools.js +31 -2
- package/dist/agents/models-config.providers.js +4 -4
- package/dist/agents/transcript-policy.js +2 -1
- package/dist/build-info.json +3 -3
- package/dist/canvas-host/a2ui/.bundle.hash +1 -1
- package/dist/cli/banner.js +6 -6
- package/dist/commands/onboard-helpers.js +6 -6
- package/dist/config/paths.js +3 -0
- package/dist/discord/send.outbound.js +4 -4
- package/dist/discord/send.shared.js +30 -1
- package/dist/discord/targets.js +66 -0
- package/dist/telegram/bot/helpers.js +13 -2
- package/dist/telegram/bot-message-context.js +4 -2
- package/dist/telegram/bot-native-commands.js +13 -8
- package/dist/telegram/bot.js +7 -5
- package/dist/telegram/monitor.js +19 -1
- package/dist/telegram/network-errors.js +4 -0
- package/docs/concepts/model-providers.md +4 -3
- package/docs/gateway/configuration.md +5 -5
- package/docs/providers/moonshot.md +13 -2
- package/package.json +3 -3
- package/skills/bitwarden/SKILL.md +101 -0
- package/skills/bitwarden/references/templates.md +116 -0
- package/skills/bitwarden/scripts/bw-session.sh +33 -0
- package/extensions/googlechat/node_modules/.bin/clawdbot +0 -21
- package/extensions/googlechat/node_modules/.bin/moltbot +0 -21
- package/extensions/line/node_modules/.bin/clawdbot +0 -21
- package/extensions/line/node_modules/.bin/moltbot +0 -21
- package/extensions/memory-core/node_modules/.bin/clawdbot +0 -21
- package/extensions/memory-core/node_modules/.bin/moltbot +0 -21
- package/extensions/msteams/node_modules/.bin/clawdbot +0 -21
- package/extensions/msteams/node_modules/.bin/moltbot +0 -21
- package/extensions/nostr/node_modules/.bin/clawdbot +0 -21
- package/extensions/nostr/node_modules/.bin/moltbot +0 -21
- package/extensions/twitch/node_modules/.bin/clawdbot +0 -21
- package/extensions/twitch/node_modules/.bin/moltbot +0 -21
- package/extensions/zalo/node_modules/.bin/clawdbot +0 -21
- package/extensions/zalo/node_modules/.bin/moltbot +0 -21
- package/extensions/zalouser/node_modules/.bin/clawdbot +0 -21
- package/extensions/zalouser/node_modules/.bin/moltbot +0 -21
package/CHANGELOG.md
CHANGED
|
@@ -77,6 +77,13 @@ Status: beta.
|
|
|
77
77
|
- macOS: auto-scroll to bottom when sending a new message while scrolled up. (#2471) Thanks @kennyklee.
|
|
78
78
|
- Web UI: auto-expand the chat compose textarea while typing (with sensible max height). (#2950) Thanks @shivamraut101.
|
|
79
79
|
- Gateway: prevent crashes on transient network errors (fetch failures, timeouts, DNS). Added fatal error detection to only exit on truly critical errors. Fixes #2895, #2879, #2873. (#2980) Thanks @elliotsecops.
|
|
80
|
+
- Agents: guard channel tool listActions to avoid plugin crashes. (#2859) Thanks @mbelinky.
|
|
81
|
+
- Discord: avoid resolving bare channel names to user DMs when a username matches. Thanks @thewilloftheshadow.
|
|
82
|
+
- Discord: fix directory config type import for target resolution. Thanks @thewilloftheshadow.
|
|
83
|
+
- Providers: update MiniMax API endpoint and compatibility mode. (#3064) Thanks @hlbbbbbbb.
|
|
84
|
+
- Telegram: treat more network errors as recoverable in polling. (#3013) Thanks @ryancontent.
|
|
85
|
+
- Discord: resolve usernames to user IDs for outbound messages. (#2649) Thanks @nonggialiang.
|
|
86
|
+
- Providers: update Moonshot Kimi model references to kimi-k2.5. (#2762) Thanks @MarvinCui.
|
|
80
87
|
- Gateway: suppress AbortError and transient network errors in unhandled rejections. (#2451) Thanks @Glucksberg.
|
|
81
88
|
- TTS: keep /tts status replies on text-only commands and avoid duplicate block-stream audio. (#2451) Thanks @Glucksberg.
|
|
82
89
|
- Security: pin npm overrides to keep tar@7.5.4 for install toolchains.
|
|
@@ -90,6 +97,7 @@ Status: beta.
|
|
|
90
97
|
- Agents: release session locks on process termination and cover more signals. (#2483) Thanks @janeexai.
|
|
91
98
|
- Agents: skip cooldowned providers during model failover. (#2143) Thanks @YiWang24.
|
|
92
99
|
- Telegram: harden polling + retry behavior for transient network errors and Node 22 transport issues. (#2420) Thanks @techboss.
|
|
100
|
+
- Telegram: ignore non-forum group message_thread_id while preserving DM thread sessions. (#2731) Thanks @dylanneve1.
|
|
93
101
|
- Telegram: wrap reasoning italics per line to avoid raw underscores. (#2181) Thanks @YuriNachos.
|
|
94
102
|
- Telegram: centralize API error logging for delivery and bot calls. (#2492) Thanks @altryne.
|
|
95
103
|
- Voice Call: enforce Twilio webhook signature verification for ngrok URLs; disable ngrok free tier bypass by default.
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { getChannelDock } from "../channels/dock.js";
|
|
2
2
|
import { getChannelPlugin, listChannelPlugins } from "../channels/plugins/index.js";
|
|
3
3
|
import { normalizeAnyChannelId } from "../channels/registry.js";
|
|
4
|
+
import { defaultRuntime } from "../runtime.js";
|
|
4
5
|
/**
|
|
5
6
|
* Get the list of supported message actions for a specific channel.
|
|
6
7
|
* Returns an empty array if channel is not found or has no actions configured.
|
|
@@ -12,7 +13,7 @@ export function listChannelSupportedActions(params) {
|
|
|
12
13
|
if (!plugin?.actions?.listActions)
|
|
13
14
|
return [];
|
|
14
15
|
const cfg = params.cfg ?? {};
|
|
15
|
-
return plugin
|
|
16
|
+
return runPluginListActions(plugin, cfg);
|
|
16
17
|
}
|
|
17
18
|
/**
|
|
18
19
|
* Get the list of all supported message actions across all configured channels.
|
|
@@ -23,7 +24,7 @@ export function listAllChannelSupportedActions(params) {
|
|
|
23
24
|
if (!plugin.actions?.listActions)
|
|
24
25
|
continue;
|
|
25
26
|
const cfg = params.cfg ?? {};
|
|
26
|
-
const channelActions = plugin
|
|
27
|
+
const channelActions = runPluginListActions(plugin, cfg);
|
|
27
28
|
for (const action of channelActions) {
|
|
28
29
|
actions.add(action);
|
|
29
30
|
}
|
|
@@ -56,3 +57,31 @@ export function resolveChannelMessageToolHints(params) {
|
|
|
56
57
|
.map((entry) => entry.trim())
|
|
57
58
|
.filter(Boolean);
|
|
58
59
|
}
|
|
60
|
+
const loggedListActionErrors = new Set();
|
|
61
|
+
function runPluginListActions(plugin, cfg) {
|
|
62
|
+
if (!plugin.actions?.listActions)
|
|
63
|
+
return [];
|
|
64
|
+
try {
|
|
65
|
+
const listed = plugin.actions.listActions({ cfg });
|
|
66
|
+
return Array.isArray(listed) ? listed : [];
|
|
67
|
+
}
|
|
68
|
+
catch (err) {
|
|
69
|
+
logListActionsError(plugin.id, err);
|
|
70
|
+
return [];
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function logListActionsError(pluginId, err) {
|
|
74
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
75
|
+
const key = `${pluginId}:${message}`;
|
|
76
|
+
if (loggedListActionErrors.has(key))
|
|
77
|
+
return;
|
|
78
|
+
loggedListActionErrors.add(key);
|
|
79
|
+
const stack = err instanceof Error && err.stack ? err.stack : null;
|
|
80
|
+
const details = stack ?? message;
|
|
81
|
+
defaultRuntime.error?.(`[channel-tools] ${pluginId}.actions.listActions failed: ${details}`);
|
|
82
|
+
}
|
|
83
|
+
export const __testing = {
|
|
84
|
+
resetLoggedListActionErrors() {
|
|
85
|
+
loggedListActionErrors.clear();
|
|
86
|
+
},
|
|
87
|
+
};
|
|
@@ -4,7 +4,7 @@ import { resolveAwsSdkEnvVarName, resolveEnvApiKey } from "./model-auth.js";
|
|
|
4
4
|
import { discoverBedrockModels } from "./bedrock-discovery.js";
|
|
5
5
|
import { buildSyntheticModelDefinition, SYNTHETIC_BASE_URL, SYNTHETIC_MODEL_CATALOG, } from "./synthetic-models.js";
|
|
6
6
|
import { discoverVeniceModels, VENICE_BASE_URL } from "./venice-models.js";
|
|
7
|
-
const MINIMAX_API_BASE_URL = "https://api.minimax.
|
|
7
|
+
const MINIMAX_API_BASE_URL = "https://api.minimax.chat/v1";
|
|
8
8
|
const MINIMAX_DEFAULT_MODEL_ID = "MiniMax-M2.1";
|
|
9
9
|
const MINIMAX_DEFAULT_VISION_MODEL_ID = "MiniMax-VL-01";
|
|
10
10
|
const MINIMAX_DEFAULT_CONTEXT_WINDOW = 200000;
|
|
@@ -17,7 +17,7 @@ const MINIMAX_API_COST = {
|
|
|
17
17
|
cacheWrite: 10,
|
|
18
18
|
};
|
|
19
19
|
const MOONSHOT_BASE_URL = "https://api.moonshot.ai/v1";
|
|
20
|
-
const MOONSHOT_DEFAULT_MODEL_ID = "kimi-k2
|
|
20
|
+
const MOONSHOT_DEFAULT_MODEL_ID = "kimi-k2.5";
|
|
21
21
|
const MOONSHOT_DEFAULT_CONTEXT_WINDOW = 256000;
|
|
22
22
|
const MOONSHOT_DEFAULT_MAX_TOKENS = 8192;
|
|
23
23
|
const MOONSHOT_DEFAULT_COST = {
|
|
@@ -198,7 +198,7 @@ export function normalizeProviders(params) {
|
|
|
198
198
|
function buildMinimaxProvider() {
|
|
199
199
|
return {
|
|
200
200
|
baseUrl: MINIMAX_API_BASE_URL,
|
|
201
|
-
api: "
|
|
201
|
+
api: "openai-completions",
|
|
202
202
|
models: [
|
|
203
203
|
{
|
|
204
204
|
id: MINIMAX_DEFAULT_MODEL_ID,
|
|
@@ -228,7 +228,7 @@ function buildMoonshotProvider() {
|
|
|
228
228
|
models: [
|
|
229
229
|
{
|
|
230
230
|
id: MOONSHOT_DEFAULT_MODEL_ID,
|
|
231
|
-
name: "Kimi K2
|
|
231
|
+
name: "Kimi K2.5",
|
|
232
232
|
reasoning: false,
|
|
233
233
|
input: ["text"],
|
|
234
234
|
cost: MOONSHOT_DEFAULT_COST,
|
|
@@ -30,7 +30,8 @@ function isAnthropicApi(modelApi, provider) {
|
|
|
30
30
|
if (modelApi === "anthropic-messages")
|
|
31
31
|
return true;
|
|
32
32
|
const normalized = normalizeProviderId(provider ?? "");
|
|
33
|
-
|
|
33
|
+
// MiniMax now uses openai-completions API, not anthropic-messages
|
|
34
|
+
return normalized === "anthropic";
|
|
34
35
|
}
|
|
35
36
|
function isMistralModel(params) {
|
|
36
37
|
const provider = normalizeProviderId(params.provider ?? "");
|
package/dist/build-info.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
5ab92d64997cf901fe504836ff46635580f54cac27a47cefee42eb50bfced85f
|
package/dist/cli/banner.js
CHANGED
|
@@ -46,12 +46,12 @@ export function formatCliBannerLine(version, options = {}) {
|
|
|
46
46
|
return `${line1}\n${line2}`;
|
|
47
47
|
}
|
|
48
48
|
const LOBSTER_ASCII = [
|
|
49
|
-
"
|
|
50
|
-
"
|
|
51
|
-
"
|
|
52
|
-
"
|
|
53
|
-
"
|
|
54
|
-
"
|
|
49
|
+
"▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄",
|
|
50
|
+
"██░▄▀▄░██░▄▄▄░██░████▄▄░▄▄██░▄▄▀██░▄▄▄░█▄▄░▄▄██",
|
|
51
|
+
"██░█░█░██░███░██░██████░████░▄▄▀██░███░███░████",
|
|
52
|
+
"██░███░██░▀▀▀░██░▀▀░███░████░▀▀░██░▀▀▀░███░████",
|
|
53
|
+
"▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀",
|
|
54
|
+
" 🦞 FRESH DAILY 🦞 ",
|
|
55
55
|
];
|
|
56
56
|
export function formatCliBannerArt(options = {}) {
|
|
57
57
|
const rich = options.richTty ?? isRich();
|
|
@@ -53,12 +53,12 @@ export function randomToken() {
|
|
|
53
53
|
}
|
|
54
54
|
export function printWizardHeader(runtime) {
|
|
55
55
|
const header = [
|
|
56
|
-
"
|
|
57
|
-
"
|
|
58
|
-
"
|
|
59
|
-
"
|
|
60
|
-
"
|
|
61
|
-
"
|
|
56
|
+
"▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄",
|
|
57
|
+
"██░▄▀▄░██░▄▄▄░██░████▄▄░▄▄██░▄▄▀██░▄▄▄░█▄▄░▄▄██",
|
|
58
|
+
"██░█░█░██░███░██░██████░████░▄▄▀██░███░███░████",
|
|
59
|
+
"██░███░██░▀▀▀░██░▀▀░███░████░▀▀░██░▀▀▀░███░████",
|
|
60
|
+
"▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀",
|
|
61
|
+
" 🦞 FRESH DAILY 🦞 ",
|
|
62
62
|
].join("\n");
|
|
63
63
|
runtime.log(header);
|
|
64
64
|
}
|
package/dist/config/paths.js
CHANGED
|
@@ -93,6 +93,7 @@ export function resolveConfigPath(env = process.env, stateDir = resolveStateDir(
|
|
|
93
93
|
const override = env.MOLTBOT_CONFIG_PATH?.trim() || env.CLAWDBOT_CONFIG_PATH?.trim();
|
|
94
94
|
if (override)
|
|
95
95
|
return resolveUserPath(override);
|
|
96
|
+
const stateOverride = env.MOLTBOT_STATE_DIR?.trim() || env.CLAWDBOT_STATE_DIR?.trim();
|
|
96
97
|
const candidates = [
|
|
97
98
|
path.join(stateDir, CONFIG_FILENAME),
|
|
98
99
|
path.join(stateDir, LEGACY_CONFIG_FILENAME),
|
|
@@ -107,6 +108,8 @@ export function resolveConfigPath(env = process.env, stateDir = resolveStateDir(
|
|
|
107
108
|
});
|
|
108
109
|
if (existing)
|
|
109
110
|
return existing;
|
|
111
|
+
if (stateOverride)
|
|
112
|
+
return path.join(stateDir, CONFIG_FILENAME);
|
|
110
113
|
const defaultStateDir = resolveStateDir(env, homedir);
|
|
111
114
|
if (path.resolve(stateDir) === path.resolve(defaultStateDir)) {
|
|
112
115
|
return resolveConfigPathCandidate(env, homedir);
|
|
@@ -5,7 +5,7 @@ import { resolveMarkdownTableMode } from "../config/markdown-tables.js";
|
|
|
5
5
|
import { recordChannelActivity } from "../infra/channel-activity.js";
|
|
6
6
|
import { convertMarkdownTables } from "../markdown/tables.js";
|
|
7
7
|
import { resolveDiscordAccount } from "./accounts.js";
|
|
8
|
-
import { buildDiscordSendError, createDiscordClient, normalizeDiscordPollInput, normalizeStickerIds,
|
|
8
|
+
import { buildDiscordSendError, createDiscordClient, normalizeDiscordPollInput, normalizeStickerIds, parseAndResolveRecipient, resolveChannelId, sendDiscordMedia, sendDiscordText, } from "./send.shared.js";
|
|
9
9
|
export async function sendMessageDiscord(to, text, opts = {}) {
|
|
10
10
|
const cfg = loadConfig();
|
|
11
11
|
const accountInfo = resolveDiscordAccount({
|
|
@@ -20,7 +20,7 @@ export async function sendMessageDiscord(to, text, opts = {}) {
|
|
|
20
20
|
const chunkMode = resolveChunkMode(cfg, "discord", accountInfo.accountId);
|
|
21
21
|
const textWithTables = convertMarkdownTables(text ?? "", tableMode);
|
|
22
22
|
const { token, rest, request } = createDiscordClient(opts, cfg);
|
|
23
|
-
const recipient =
|
|
23
|
+
const recipient = await parseAndResolveRecipient(to, opts.accountId);
|
|
24
24
|
const { channelId } = await resolveChannelId(rest, recipient, request);
|
|
25
25
|
let result;
|
|
26
26
|
try {
|
|
@@ -52,7 +52,7 @@ export async function sendMessageDiscord(to, text, opts = {}) {
|
|
|
52
52
|
export async function sendStickerDiscord(to, stickerIds, opts = {}) {
|
|
53
53
|
const cfg = loadConfig();
|
|
54
54
|
const { rest, request } = createDiscordClient(opts, cfg);
|
|
55
|
-
const recipient =
|
|
55
|
+
const recipient = await parseAndResolveRecipient(to, opts.accountId);
|
|
56
56
|
const { channelId } = await resolveChannelId(rest, recipient, request);
|
|
57
57
|
const content = opts.content?.trim();
|
|
58
58
|
const stickers = normalizeStickerIds(stickerIds);
|
|
@@ -70,7 +70,7 @@ export async function sendStickerDiscord(to, stickerIds, opts = {}) {
|
|
|
70
70
|
export async function sendPollDiscord(to, poll, opts = {}) {
|
|
71
71
|
const cfg = loadConfig();
|
|
72
72
|
const { rest, request } = createDiscordClient(opts, cfg);
|
|
73
|
-
const recipient =
|
|
73
|
+
const recipient = await parseAndResolveRecipient(to, opts.accountId);
|
|
74
74
|
const { channelId } = await resolveChannelId(rest, recipient, request);
|
|
75
75
|
const content = opts.content?.trim();
|
|
76
76
|
const payload = normalizeDiscordPollInput(poll);
|
|
@@ -9,7 +9,7 @@ import { resolveDiscordAccount } from "./accounts.js";
|
|
|
9
9
|
import { chunkDiscordTextWithMode } from "./chunk.js";
|
|
10
10
|
import { fetchChannelPermissionsDiscord, isThreadChannelType } from "./send.permissions.js";
|
|
11
11
|
import { DiscordSendError } from "./send.types.js";
|
|
12
|
-
import { parseDiscordTarget } from "./targets.js";
|
|
12
|
+
import { parseDiscordTarget, resolveDiscordTarget } from "./targets.js";
|
|
13
13
|
import { normalizeDiscordToken } from "./token.js";
|
|
14
14
|
const DISCORD_TEXT_LIMIT = 2000;
|
|
15
15
|
const DISCORD_MAX_STICKERS = 3;
|
|
@@ -68,6 +68,35 @@ function parseRecipient(raw) {
|
|
|
68
68
|
}
|
|
69
69
|
return { kind: target.kind, id: target.id };
|
|
70
70
|
}
|
|
71
|
+
/**
|
|
72
|
+
* Parse and resolve Discord recipient, including username lookup.
|
|
73
|
+
* This enables sending DMs by username (e.g., "john.doe") by querying
|
|
74
|
+
* the Discord directory to resolve usernames to user IDs.
|
|
75
|
+
*
|
|
76
|
+
* @param raw - The recipient string (username, ID, or known format)
|
|
77
|
+
* @param accountId - Discord account ID to use for directory lookup
|
|
78
|
+
* @returns Parsed DiscordRecipient with resolved user ID if applicable
|
|
79
|
+
*/
|
|
80
|
+
export async function parseAndResolveRecipient(raw, accountId) {
|
|
81
|
+
const cfg = loadConfig();
|
|
82
|
+
const accountInfo = resolveDiscordAccount({ cfg, accountId });
|
|
83
|
+
// First try to resolve using directory lookup (handles usernames)
|
|
84
|
+
const resolved = await resolveDiscordTarget(raw, {
|
|
85
|
+
cfg,
|
|
86
|
+
accountId: accountInfo.accountId,
|
|
87
|
+
});
|
|
88
|
+
if (resolved) {
|
|
89
|
+
return { kind: resolved.kind, id: resolved.id };
|
|
90
|
+
}
|
|
91
|
+
// Fallback to standard parsing (for channels, etc.)
|
|
92
|
+
const parsed = parseDiscordTarget(raw, {
|
|
93
|
+
ambiguousMessage: `Ambiguous Discord recipient "${raw.trim()}". Use "user:${raw.trim()}" for DMs or "channel:${raw.trim()}" for channel messages.`,
|
|
94
|
+
});
|
|
95
|
+
if (!parsed) {
|
|
96
|
+
throw new Error("Recipient is required for Discord sends");
|
|
97
|
+
}
|
|
98
|
+
return { kind: parsed.kind, id: parsed.id };
|
|
99
|
+
}
|
|
71
100
|
function normalizeStickerIds(raw) {
|
|
72
101
|
const ids = raw.map((entry) => entry.trim()).filter(Boolean);
|
|
73
102
|
if (ids.length === 0) {
|
package/dist/discord/targets.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { buildMessagingTarget, ensureTargetId, requireTargetKind, } from "../channels/targets.js";
|
|
2
|
+
import { listDiscordDirectoryPeersLive } from "./directory-live.js";
|
|
2
3
|
export function parseDiscordTarget(raw, options = {}) {
|
|
3
4
|
const trimmed = raw.trim();
|
|
4
5
|
if (!trimmed)
|
|
@@ -41,3 +42,68 @@ export function resolveDiscordChannelId(raw) {
|
|
|
41
42
|
const target = parseDiscordTarget(raw, { defaultKind: "channel" });
|
|
42
43
|
return requireTargetKind({ platform: "Discord", target, kind: "channel" });
|
|
43
44
|
}
|
|
45
|
+
/**
|
|
46
|
+
* Resolve a Discord username to user ID using the directory lookup.
|
|
47
|
+
* This enables sending DMs by username instead of requiring explicit user IDs.
|
|
48
|
+
*
|
|
49
|
+
* @param raw - The username or raw target string (e.g., "john.doe")
|
|
50
|
+
* @param options - Directory configuration params (cfg, accountId, limit)
|
|
51
|
+
* @param parseOptions - Optional parsing options (defaultKind, ambiguousMessage)
|
|
52
|
+
* @returns Parsed MessagingTarget with user ID, or undefined if not found
|
|
53
|
+
*/
|
|
54
|
+
export async function resolveDiscordTarget(raw, options, parseOptions = {}) {
|
|
55
|
+
const trimmed = raw.trim();
|
|
56
|
+
if (!trimmed)
|
|
57
|
+
return undefined;
|
|
58
|
+
const shouldLookup = isExplicitUserLookup(trimmed, parseOptions);
|
|
59
|
+
const directParse = safeParseDiscordTarget(trimmed, parseOptions);
|
|
60
|
+
if (directParse && directParse.kind !== "channel") {
|
|
61
|
+
return directParse;
|
|
62
|
+
}
|
|
63
|
+
if (!shouldLookup) {
|
|
64
|
+
return directParse ?? parseDiscordTarget(trimmed, parseOptions);
|
|
65
|
+
}
|
|
66
|
+
// Try to resolve as a username via directory lookup
|
|
67
|
+
try {
|
|
68
|
+
const directoryEntries = await listDiscordDirectoryPeersLive({
|
|
69
|
+
...options,
|
|
70
|
+
query: trimmed,
|
|
71
|
+
limit: 1,
|
|
72
|
+
});
|
|
73
|
+
const match = directoryEntries[0];
|
|
74
|
+
if (match && match.kind === "user") {
|
|
75
|
+
// Extract user ID from the directory entry (format: "user:<id>")
|
|
76
|
+
const userId = match.id.replace(/^user:/, "");
|
|
77
|
+
return buildMessagingTarget("user", userId, trimmed);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
// Directory lookup failed - fall through to parse as-is
|
|
82
|
+
// This preserves existing behavior for channel names
|
|
83
|
+
}
|
|
84
|
+
// Fallback to original parsing (for channels, etc.)
|
|
85
|
+
return parseDiscordTarget(trimmed, parseOptions);
|
|
86
|
+
}
|
|
87
|
+
function safeParseDiscordTarget(input, options) {
|
|
88
|
+
try {
|
|
89
|
+
return parseDiscordTarget(input, options);
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
return undefined;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
function isExplicitUserLookup(input, options) {
|
|
96
|
+
if (/^<@!?(\d+)>$/.test(input)) {
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
if (/^(user:|discord:)/.test(input)) {
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
if (input.startsWith("@")) {
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
if (/^\d+$/.test(input)) {
|
|
106
|
+
return options.defaultKind === "user";
|
|
107
|
+
}
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
@@ -1,10 +1,21 @@
|
|
|
1
1
|
import { formatLocationText } from "../../channels/location.js";
|
|
2
2
|
const TELEGRAM_GENERAL_TOPIC_ID = 1;
|
|
3
|
+
/**
|
|
4
|
+
* Resolve the thread ID for Telegram forum topics.
|
|
5
|
+
* For non-forum groups, returns undefined even if messageThreadId is present
|
|
6
|
+
* (reply threads in regular groups should not create separate sessions).
|
|
7
|
+
* For forum groups, returns the topic ID (or General topic ID=1 if unspecified).
|
|
8
|
+
*/
|
|
3
9
|
export function resolveTelegramForumThreadId(params) {
|
|
4
|
-
|
|
10
|
+
// Non-forum groups: ignore message_thread_id (reply threads are not real topics)
|
|
11
|
+
if (!params.isForum) {
|
|
12
|
+
return undefined;
|
|
13
|
+
}
|
|
14
|
+
// Forum groups: use the topic ID, defaulting to General topic
|
|
15
|
+
if (params.messageThreadId == null) {
|
|
5
16
|
return TELEGRAM_GENERAL_TOPIC_ID;
|
|
6
17
|
}
|
|
7
|
-
return params.messageThreadId
|
|
18
|
+
return params.messageThreadId;
|
|
8
19
|
}
|
|
9
20
|
/**
|
|
10
21
|
* Build thread params for Telegram API calls (messages, media).
|
|
@@ -66,7 +66,8 @@ export const buildTelegramMessageContext = async ({ primaryCtx, allMedia, storeA
|
|
|
66
66
|
},
|
|
67
67
|
});
|
|
68
68
|
const baseSessionKey = route.sessionKey;
|
|
69
|
-
|
|
69
|
+
// DMs: use raw messageThreadId for thread sessions (not resolvedThreadId which is for forums)
|
|
70
|
+
const dmThreadId = !isGroup ? messageThreadId : undefined;
|
|
70
71
|
const threadKeys = dmThreadId != null
|
|
71
72
|
? resolveThreadSessionKeys({ baseSessionKey, threadId: String(dmThreadId) })
|
|
72
73
|
: null;
|
|
@@ -438,7 +439,8 @@ export const buildTelegramMessageContext = async ({ primaryCtx, allMedia, storeA
|
|
|
438
439
|
Sticker: allMedia[0]?.stickerMetadata,
|
|
439
440
|
...(locationData ? toLocationContext(locationData) : undefined),
|
|
440
441
|
CommandAuthorized: commandAuthorized,
|
|
441
|
-
|
|
442
|
+
// For groups: use resolvedThreadId (forum topics only); for DMs: use raw messageThreadId
|
|
443
|
+
MessageThreadId: isGroup ? resolvedThreadId : messageThreadId,
|
|
442
444
|
IsForum: isForum,
|
|
443
445
|
// Originating channel for reply routing.
|
|
444
446
|
OriginatingChannel: "telegram",
|
|
@@ -189,7 +189,7 @@ export const registerTelegramNativeCommands = ({ bot, cfg, runtime, accountId, t
|
|
|
189
189
|
...customCommands,
|
|
190
190
|
];
|
|
191
191
|
if (allCommands.length > 0) {
|
|
192
|
-
|
|
192
|
+
withTelegramApiErrorLogging({
|
|
193
193
|
operation: "setMyCommands",
|
|
194
194
|
runtime,
|
|
195
195
|
fn: () => bot.api.setMyCommands(allCommands),
|
|
@@ -220,6 +220,8 @@ export const registerTelegramNativeCommands = ({ bot, cfg, runtime, accountId, t
|
|
|
220
220
|
if (!auth)
|
|
221
221
|
return;
|
|
222
222
|
const { chatId, isGroup, isForum, resolvedThreadId, senderId, senderUsername, groupConfig, topicConfig, commandAuthorized, } = auth;
|
|
223
|
+
const messageThreadId = msg.message_thread_id;
|
|
224
|
+
const threadIdForSend = isGroup ? resolvedThreadId : messageThreadId;
|
|
223
225
|
const commandDefinition = findCommandByNativeName(command.name, "telegram");
|
|
224
226
|
const rawText = ctx.match?.trim() ?? "";
|
|
225
227
|
const commandArgs = commandDefinition
|
|
@@ -261,7 +263,7 @@ export const registerTelegramNativeCommands = ({ bot, cfg, runtime, accountId, t
|
|
|
261
263
|
runtime,
|
|
262
264
|
fn: () => bot.api.sendMessage(chatId, title, {
|
|
263
265
|
...(replyMarkup ? { reply_markup: replyMarkup } : {}),
|
|
264
|
-
...(
|
|
266
|
+
...(threadIdForSend != null ? { message_thread_id: threadIdForSend } : {}),
|
|
265
267
|
}),
|
|
266
268
|
});
|
|
267
269
|
return;
|
|
@@ -276,7 +278,8 @@ export const registerTelegramNativeCommands = ({ bot, cfg, runtime, accountId, t
|
|
|
276
278
|
},
|
|
277
279
|
});
|
|
278
280
|
const baseSessionKey = route.sessionKey;
|
|
279
|
-
|
|
281
|
+
// DMs: use raw messageThreadId for thread sessions (not resolvedThreadId which is for forums)
|
|
282
|
+
const dmThreadId = !isGroup ? messageThreadId : undefined;
|
|
280
283
|
const threadKeys = dmThreadId != null
|
|
281
284
|
? resolveThreadSessionKeys({ baseSessionKey, threadId: String(dmThreadId) })
|
|
282
285
|
: null;
|
|
@@ -319,7 +322,7 @@ export const registerTelegramNativeCommands = ({ bot, cfg, runtime, accountId, t
|
|
|
319
322
|
CommandSource: "native",
|
|
320
323
|
SessionKey: `telegram:slash:${senderId || chatId}`,
|
|
321
324
|
CommandTargetSessionKey: sessionKey,
|
|
322
|
-
MessageThreadId:
|
|
325
|
+
MessageThreadId: threadIdForSend,
|
|
323
326
|
IsForum: isForum,
|
|
324
327
|
// Originating context for sub-agent announce routing
|
|
325
328
|
OriginatingChannel: "telegram",
|
|
@@ -343,7 +346,7 @@ export const registerTelegramNativeCommands = ({ bot, cfg, runtime, accountId, t
|
|
|
343
346
|
bot,
|
|
344
347
|
replyToMode,
|
|
345
348
|
textLimit,
|
|
346
|
-
messageThreadId:
|
|
349
|
+
messageThreadId: threadIdForSend,
|
|
347
350
|
tableMode,
|
|
348
351
|
chunkMode,
|
|
349
352
|
linkPreview: telegramCfg.linkPreview,
|
|
@@ -393,7 +396,9 @@ export const registerTelegramNativeCommands = ({ bot, cfg, runtime, accountId, t
|
|
|
393
396
|
});
|
|
394
397
|
if (!auth)
|
|
395
398
|
return;
|
|
396
|
-
const { resolvedThreadId, senderId, commandAuthorized } = auth;
|
|
399
|
+
const { resolvedThreadId, senderId, commandAuthorized, isGroup } = auth;
|
|
400
|
+
const messageThreadId = msg.message_thread_id;
|
|
401
|
+
const threadIdForSend = isGroup ? resolvedThreadId : messageThreadId;
|
|
397
402
|
const result = await executePluginCommand({
|
|
398
403
|
command: match.command,
|
|
399
404
|
args: match.args,
|
|
@@ -417,7 +422,7 @@ export const registerTelegramNativeCommands = ({ bot, cfg, runtime, accountId, t
|
|
|
417
422
|
bot,
|
|
418
423
|
replyToMode,
|
|
419
424
|
textLimit,
|
|
420
|
-
messageThreadId:
|
|
425
|
+
messageThreadId: threadIdForSend,
|
|
421
426
|
tableMode,
|
|
422
427
|
chunkMode,
|
|
423
428
|
linkPreview: telegramCfg.linkPreview,
|
|
@@ -427,7 +432,7 @@ export const registerTelegramNativeCommands = ({ bot, cfg, runtime, accountId, t
|
|
|
427
432
|
}
|
|
428
433
|
}
|
|
429
434
|
else if (nativeDisabledExplicit) {
|
|
430
|
-
|
|
435
|
+
withTelegramApiErrorLogging({
|
|
431
436
|
operation: "setMyCommands",
|
|
432
437
|
runtime,
|
|
433
438
|
fn: () => bot.api.setMyCommands([]),
|
package/dist/telegram/bot.js
CHANGED
|
@@ -45,11 +45,12 @@ export function getTelegramSequentialKey(ctx) {
|
|
|
45
45
|
return `telegram:${chatId}:control`;
|
|
46
46
|
return "telegram:control";
|
|
47
47
|
}
|
|
48
|
+
const isGroup = msg?.chat?.type === "group" || msg?.chat?.type === "supergroup";
|
|
49
|
+
const messageThreadId = msg?.message_thread_id;
|
|
48
50
|
const isForum = msg?.chat?.is_forum;
|
|
49
|
-
const threadId =
|
|
50
|
-
isForum,
|
|
51
|
-
|
|
52
|
-
});
|
|
51
|
+
const threadId = isGroup
|
|
52
|
+
? resolveTelegramForumThreadId({ isForum, messageThreadId })
|
|
53
|
+
: messageThreadId;
|
|
53
54
|
if (typeof chatId === "number") {
|
|
54
55
|
return threadId != null ? `telegram:${chatId}:topic:${threadId}` : `telegram:${chatId}`;
|
|
55
56
|
}
|
|
@@ -357,7 +358,8 @@ export function createTelegramBot(opts) {
|
|
|
357
358
|
peer: { kind: isGroup ? "group" : "dm", id: peerId },
|
|
358
359
|
});
|
|
359
360
|
const baseSessionKey = route.sessionKey;
|
|
360
|
-
|
|
361
|
+
// DMs: use raw messageThreadId for thread sessions (not resolvedThreadId which is for forums)
|
|
362
|
+
const dmThreadId = !isGroup ? messageThreadId : undefined;
|
|
361
363
|
const threadKeys = dmThreadId != null
|
|
362
364
|
? resolveThreadSessionKeys({ baseSessionKey, threadId: String(dmThreadId) })
|
|
363
365
|
: null;
|
package/dist/telegram/monitor.js
CHANGED
|
@@ -50,6 +50,23 @@ const isGetUpdatesConflict = (err) => {
|
|
|
50
50
|
.toLowerCase();
|
|
51
51
|
return haystack.includes("getupdates");
|
|
52
52
|
};
|
|
53
|
+
const NETWORK_ERROR_SNIPPETS = [
|
|
54
|
+
"fetch failed",
|
|
55
|
+
"network",
|
|
56
|
+
"timeout",
|
|
57
|
+
"socket",
|
|
58
|
+
"econnreset",
|
|
59
|
+
"econnrefused",
|
|
60
|
+
"undici",
|
|
61
|
+
];
|
|
62
|
+
const isNetworkRelatedError = (err) => {
|
|
63
|
+
if (!err)
|
|
64
|
+
return false;
|
|
65
|
+
const message = formatErrorMessage(err).toLowerCase();
|
|
66
|
+
if (!message)
|
|
67
|
+
return false;
|
|
68
|
+
return NETWORK_ERROR_SNIPPETS.some((snippet) => message.includes(snippet));
|
|
69
|
+
};
|
|
53
70
|
export async function monitorTelegramProvider(opts = {}) {
|
|
54
71
|
const cfg = opts.config ?? loadConfig();
|
|
55
72
|
const account = resolveTelegramAccount({
|
|
@@ -126,7 +143,8 @@ export async function monitorTelegramProvider(opts = {}) {
|
|
|
126
143
|
}
|
|
127
144
|
const isConflict = isGetUpdatesConflict(err);
|
|
128
145
|
const isRecoverable = isRecoverableTelegramNetworkError(err, { context: "polling" });
|
|
129
|
-
|
|
146
|
+
const isNetworkError = isNetworkRelatedError(err);
|
|
147
|
+
if (!isConflict && !isRecoverable && !isNetworkError) {
|
|
130
148
|
throw err;
|
|
131
149
|
}
|
|
132
150
|
restartAttempts += 1;
|
|
@@ -14,6 +14,8 @@ const RECOVERABLE_ERROR_CODES = new Set([
|
|
|
14
14
|
"UND_ERR_BODY_TIMEOUT",
|
|
15
15
|
"UND_ERR_SOCKET",
|
|
16
16
|
"UND_ERR_ABORTED",
|
|
17
|
+
"ECONNABORTED",
|
|
18
|
+
"ERR_NETWORK",
|
|
17
19
|
]);
|
|
18
20
|
const RECOVERABLE_ERROR_NAMES = new Set([
|
|
19
21
|
"AbortError",
|
|
@@ -24,6 +26,8 @@ const RECOVERABLE_ERROR_NAMES = new Set([
|
|
|
24
26
|
]);
|
|
25
27
|
const RECOVERABLE_MESSAGE_SNIPPETS = [
|
|
26
28
|
"fetch failed",
|
|
29
|
+
"typeerror: fetch failed",
|
|
30
|
+
"undici",
|
|
27
31
|
"network error",
|
|
28
32
|
"network request",
|
|
29
33
|
"client network socket disconnected",
|
|
@@ -130,9 +130,10 @@ Moonshot uses OpenAI-compatible endpoints, so configure it as a custom provider:
|
|
|
130
130
|
|
|
131
131
|
- Provider: `moonshot`
|
|
132
132
|
- Auth: `MOONSHOT_API_KEY`
|
|
133
|
-
- Example model: `moonshot/kimi-k2
|
|
133
|
+
- Example model: `moonshot/kimi-k2.5`
|
|
134
134
|
- Kimi K2 model IDs:
|
|
135
135
|
{/* moonshot-kimi-k2-model-refs:start */}
|
|
136
|
+
- `moonshot/kimi-k2.5`
|
|
136
137
|
- `moonshot/kimi-k2-0905-preview`
|
|
137
138
|
- `moonshot/kimi-k2-turbo-preview`
|
|
138
139
|
- `moonshot/kimi-k2-thinking`
|
|
@@ -141,7 +142,7 @@ Moonshot uses OpenAI-compatible endpoints, so configure it as a custom provider:
|
|
|
141
142
|
```json5
|
|
142
143
|
{
|
|
143
144
|
agents: {
|
|
144
|
-
defaults: { model: { primary: "moonshot/kimi-k2
|
|
145
|
+
defaults: { model: { primary: "moonshot/kimi-k2.5" } }
|
|
145
146
|
},
|
|
146
147
|
models: {
|
|
147
148
|
mode: "merge",
|
|
@@ -150,7 +151,7 @@ Moonshot uses OpenAI-compatible endpoints, so configure it as a custom provider:
|
|
|
150
151
|
baseUrl: "https://api.moonshot.ai/v1",
|
|
151
152
|
apiKey: "${MOONSHOT_API_KEY}",
|
|
152
153
|
api: "openai-completions",
|
|
153
|
-
models: [{ id: "kimi-k2
|
|
154
|
+
models: [{ id: "kimi-k2.5", name: "Kimi K2.5" }]
|
|
154
155
|
}
|
|
155
156
|
}
|
|
156
157
|
}
|
|
@@ -2396,8 +2396,8 @@ Use Moonshot's OpenAI-compatible endpoint:
|
|
|
2396
2396
|
env: { MOONSHOT_API_KEY: "sk-..." },
|
|
2397
2397
|
agents: {
|
|
2398
2398
|
defaults: {
|
|
2399
|
-
model: { primary: "moonshot/kimi-k2
|
|
2400
|
-
models: { "moonshot/kimi-k2
|
|
2399
|
+
model: { primary: "moonshot/kimi-k2.5" },
|
|
2400
|
+
models: { "moonshot/kimi-k2.5": { alias: "Kimi K2.5" } }
|
|
2401
2401
|
}
|
|
2402
2402
|
},
|
|
2403
2403
|
models: {
|
|
@@ -2409,8 +2409,8 @@ Use Moonshot's OpenAI-compatible endpoint:
|
|
|
2409
2409
|
api: "openai-completions",
|
|
2410
2410
|
models: [
|
|
2411
2411
|
{
|
|
2412
|
-
id: "kimi-k2
|
|
2413
|
-
name: "Kimi K2
|
|
2412
|
+
id: "kimi-k2.5",
|
|
2413
|
+
name: "Kimi K2.5",
|
|
2414
2414
|
reasoning: false,
|
|
2415
2415
|
input: ["text"],
|
|
2416
2416
|
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
@@ -2426,7 +2426,7 @@ Use Moonshot's OpenAI-compatible endpoint:
|
|
|
2426
2426
|
|
|
2427
2427
|
Notes:
|
|
2428
2428
|
- Set `MOONSHOT_API_KEY` in the environment or use `moltbot onboard --auth-choice moonshot-api-key`.
|
|
2429
|
-
- Model ref: `moonshot/kimi-k2
|
|
2429
|
+
- Model ref: `moonshot/kimi-k2.5`.
|
|
2430
2430
|
- Use `https://api.moonshot.cn/v1` if you need the China endpoint.
|
|
2431
2431
|
|
|
2432
2432
|
### Kimi Code
|