fix: lint errors

This commit is contained in:
Bohdan Podvirnyi
2026-01-15 18:13:49 +02:00
committed by Peter Steinberger
parent 0e1dcf9cb4
commit eb7656d68c
10 changed files with 202 additions and 572 deletions

View File

@@ -91,14 +91,7 @@ export type SessionConfig = {
export type LoggingConfig = { export type LoggingConfig = {
level?: "silent" | "fatal" | "error" | "warn" | "info" | "debug" | "trace"; level?: "silent" | "fatal" | "error" | "warn" | "info" | "debug" | "trace";
file?: string; file?: string;
consoleLevel?: consoleLevel?: "silent" | "fatal" | "error" | "warn" | "info" | "debug" | "trace";
| "silent"
| "fatal"
| "error"
| "warn"
| "info"
| "debug"
| "trace";
consoleStyle?: "pretty" | "compact" | "json"; consoleStyle?: "pretty" | "compact" | "json";
/** Redact sensitive tokens in tool summaries. Default: "tools". */ /** Redact sensitive tokens in tool summaries. Default: "tools". */
redactSensitive?: "off" | "tools"; redactSensitive?: "off" | "tools";
@@ -122,9 +115,7 @@ export type WebConfig = {
}; };
// Provider docking: allowlists keyed by provider id (and internal "webchat"). // Provider docking: allowlists keyed by provider id (and internal "webchat").
export type AgentElevatedAllowFromConfig = Partial< export type AgentElevatedAllowFromConfig = Partial<Record<string, Array<string | number>>>;
Record<string, Array<string | number>>
>;
export type IdentityConfig = { export type IdentityConfig = {
name?: string; name?: string;
@@ -495,11 +486,7 @@ export type DiscordGuildChannelConfig = {
systemPrompt?: string; systemPrompt?: string;
}; };
export type DiscordReactionNotificationMode = export type DiscordReactionNotificationMode = "off" | "own" | "all" | "allowlist";
| "off"
| "own"
| "all"
| "allowlist";
export type DiscordGuildEntry = { export type DiscordGuildEntry = {
slug?: string; slug?: string;
@@ -614,11 +601,7 @@ export type SlackChannelConfig = {
systemPrompt?: string; systemPrompt?: string;
}; };
export type SignalReactionNotificationMode = export type SignalReactionNotificationMode = "off" | "own" | "all" | "allowlist";
| "off"
| "own"
| "all"
| "allowlist";
export type SlackReactionNotificationMode = "off" | "own" | "all" | "allowlist"; export type SlackReactionNotificationMode = "off" | "own" | "all" | "allowlist";

View File

@@ -87,16 +87,8 @@ const QueueModeSchema = z.union([
z.literal("queue"), z.literal("queue"),
z.literal("interrupt"), z.literal("interrupt"),
]); ]);
const QueueDropSchema = z.union([ const QueueDropSchema = z.union([z.literal("old"), z.literal("new"), z.literal("summarize")]);
z.literal("old"), const ReplyToModeSchema = z.union([z.literal("off"), z.literal("first"), z.literal("all")]);
z.literal("new"),
z.literal("summarize"),
]);
const ReplyToModeSchema = z.union([
z.literal("off"),
z.literal("first"),
z.literal("all"),
]);
// GroupPolicySchema: controls how group messages are handled // GroupPolicySchema: controls how group messages are handled
// Used with .default("allowlist").optional() pattern: // Used with .default("allowlist").optional() pattern:
@@ -116,18 +108,12 @@ const BlockStreamingChunkSchema = z.object({
minChars: z.number().int().positive().optional(), minChars: z.number().int().positive().optional(),
maxChars: z.number().int().positive().optional(), maxChars: z.number().int().positive().optional(),
breakPreference: z breakPreference: z
.union([ .union([z.literal("paragraph"), z.literal("newline"), z.literal("sentence")])
z.literal("paragraph"),
z.literal("newline"),
z.literal("sentence"),
])
.optional(), .optional(),
}); });
const HumanDelaySchema = z.object({ const HumanDelaySchema = z.object({
mode: z mode: z.union([z.literal("off"), z.literal("natural"), z.literal("custom")]).optional(),
.union([z.literal("off"), z.literal("natural"), z.literal("custom")])
.optional(),
minMs: z.number().int().nonnegative().optional(), minMs: z.number().int().nonnegative().optional(),
maxMs: z.number().int().nonnegative().optional(), maxMs: z.number().int().nonnegative().optional(),
}); });
@@ -135,12 +121,8 @@ const HumanDelaySchema = z.object({
const CliBackendSchema = z.object({ const CliBackendSchema = z.object({
command: z.string(), command: z.string(),
args: z.array(z.string()).optional(), args: z.array(z.string()).optional(),
output: z output: z.union([z.literal("json"), z.literal("text"), z.literal("jsonl")]).optional(),
.union([z.literal("json"), z.literal("text"), z.literal("jsonl")]) resumeOutput: z.union([z.literal("json"), z.literal("text"), z.literal("jsonl")]).optional(),
.optional(),
resumeOutput: z
.union([z.literal("json"), z.literal("text"), z.literal("jsonl")])
.optional(),
input: z.union([z.literal("arg"), z.literal("stdin")]).optional(), input: z.union([z.literal("arg"), z.literal("stdin")]).optional(),
maxPromptArgChars: z.number().int().positive().optional(), maxPromptArgChars: z.number().int().positive().optional(),
env: z.record(z.string(), z.string()).optional(), env: z.record(z.string(), z.string()).optional(),
@@ -150,14 +132,10 @@ const CliBackendSchema = z.object({
sessionArg: z.string().optional(), sessionArg: z.string().optional(),
sessionArgs: z.array(z.string()).optional(), sessionArgs: z.array(z.string()).optional(),
resumeArgs: z.array(z.string()).optional(), resumeArgs: z.array(z.string()).optional(),
sessionMode: z sessionMode: z.union([z.literal("always"), z.literal("existing"), z.literal("none")]).optional(),
.union([z.literal("always"), z.literal("existing"), z.literal("none")])
.optional(),
sessionIdFields: z.array(z.string()).optional(), sessionIdFields: z.array(z.string()).optional(),
systemPromptArg: z.string().optional(), systemPromptArg: z.string().optional(),
systemPromptMode: z systemPromptMode: z.union([z.literal("append"), z.literal("replace")]).optional(),
.union([z.literal("append"), z.literal("replace")])
.optional(),
systemPromptWhen: z systemPromptWhen: z
.union([z.literal("first"), z.literal("always"), z.literal("never")]) .union([z.literal("first"), z.literal("always"), z.literal("never")])
.optional(), .optional(),
@@ -236,9 +214,7 @@ const TranscribeAudioSchema = z
}) })
.optional(); .optional();
const HexColorSchema = z const HexColorSchema = z.string().regex(/^#?[0-9a-fA-F]{6}$/, "expected hex color (RRGGBB)");
.string()
.regex(/^#?[0-9a-fA-F]{6}$/, "expected hex color (RRGGBB)");
const ExecutableTokenSchema = z const ExecutableTokenSchema = z
.string() .string()
@@ -312,8 +288,7 @@ const TelegramAccountSchemaBase = z.object({
.optional(), .optional(),
}); });
const TelegramAccountSchema = TelegramAccountSchemaBase.superRefine( const TelegramAccountSchema = TelegramAccountSchemaBase.superRefine((value, ctx) => {
(value, ctx) => {
requireOpenAllowFrom({ requireOpenAllowFrom({
policy: value.dmPolicy, policy: value.dmPolicy,
allowFrom: value.allowFrom, allowFrom: value.allowFrom,
@@ -322,8 +297,7 @@ const TelegramAccountSchema = TelegramAccountSchemaBase.superRefine(
message: message:
'channels.telegram.dmPolicy="open" requires channels.telegram.allowFrom to include "*"', 'channels.telegram.dmPolicy="open" requires channels.telegram.allowFrom to include "*"',
}); });
}, });
);
const TelegramConfigSchema = TelegramAccountSchemaBase.extend({ const TelegramConfigSchema = TelegramAccountSchemaBase.extend({
accounts: z.record(z.string(), TelegramAccountSchema.optional()).optional(), accounts: z.record(z.string(), TelegramAccountSchema.optional()).optional(),
@@ -372,9 +346,7 @@ const DiscordGuildSchema = z.object({
requireMention: z.boolean().optional(), requireMention: z.boolean().optional(),
reactionNotifications: z.enum(["off", "own", "all", "allowlist"]).optional(), reactionNotifications: z.enum(["off", "own", "all", "allowlist"]).optional(),
users: z.array(z.union([z.string(), z.number()])).optional(), users: z.array(z.union([z.string(), z.number()])).optional(),
channels: z channels: z.record(z.string(), DiscordGuildChannelSchema.optional()).optional(),
.record(z.string(), DiscordGuildChannelSchema.optional())
.optional(),
}); });
const DiscordAccountSchema = z.object({ const DiscordAccountSchema = z.object({
@@ -527,18 +499,15 @@ const SignalAccountSchemaBase = z.object({
reactionAllowlist: z.array(z.union([z.string(), z.number()])).optional(), reactionAllowlist: z.array(z.union([z.string(), z.number()])).optional(),
}); });
const SignalAccountSchema = SignalAccountSchemaBase.superRefine( const SignalAccountSchema = SignalAccountSchemaBase.superRefine((value, ctx) => {
(value, ctx) => {
requireOpenAllowFrom({ requireOpenAllowFrom({
policy: value.dmPolicy, policy: value.dmPolicy,
allowFrom: value.allowFrom, allowFrom: value.allowFrom,
ctx, ctx,
path: ["allowFrom"], path: ["allowFrom"],
message: message: 'channels.signal.dmPolicy="open" requires channels.signal.allowFrom to include "*"',
'channels.signal.dmPolicy="open" requires channels.signal.allowFrom to include "*"', });
}); });
},
);
const SignalConfigSchema = SignalAccountSchemaBase.extend({ const SignalConfigSchema = SignalAccountSchemaBase.extend({
accounts: z.record(z.string(), SignalAccountSchema.optional()).optional(), accounts: z.record(z.string(), SignalAccountSchema.optional()).optional(),
@@ -548,8 +517,7 @@ const SignalConfigSchema = SignalAccountSchemaBase.extend({
allowFrom: value.allowFrom, allowFrom: value.allowFrom,
ctx, ctx,
path: ["allowFrom"], path: ["allowFrom"],
message: message: 'channels.signal.dmPolicy="open" requires channels.signal.allowFrom to include "*"',
'channels.signal.dmPolicy="open" requires channels.signal.allowFrom to include "*"',
}); });
}); });
@@ -559,9 +527,7 @@ const IMessageAccountSchemaBase = z.object({
enabled: z.boolean().optional(), enabled: z.boolean().optional(),
cliPath: ExecutableTokenSchema.optional(), cliPath: ExecutableTokenSchema.optional(),
dbPath: z.string().optional(), dbPath: z.string().optional(),
service: z service: z.union([z.literal("imessage"), z.literal("sms"), z.literal("auto")]).optional(),
.union([z.literal("imessage"), z.literal("sms"), z.literal("auto")])
.optional(),
region: z.string().optional(), region: z.string().optional(),
dmPolicy: DmPolicySchema.optional().default("pairing"), dmPolicy: DmPolicySchema.optional().default("pairing"),
allowFrom: z.array(z.union([z.string(), z.number()])).optional(), allowFrom: z.array(z.union([z.string(), z.number()])).optional(),
@@ -587,8 +553,7 @@ const IMessageAccountSchemaBase = z.object({
.optional(), .optional(),
}); });
const IMessageAccountSchema = IMessageAccountSchemaBase.superRefine( const IMessageAccountSchema = IMessageAccountSchemaBase.superRefine((value, ctx) => {
(value, ctx) => {
requireOpenAllowFrom({ requireOpenAllowFrom({
policy: value.dmPolicy, policy: value.dmPolicy,
allowFrom: value.allowFrom, allowFrom: value.allowFrom,
@@ -597,8 +562,7 @@ const IMessageAccountSchema = IMessageAccountSchemaBase.superRefine(
message: message:
'channels.imessage.dmPolicy="open" requires channels.imessage.allowFrom to include "*"', 'channels.imessage.dmPolicy="open" requires channels.imessage.allowFrom to include "*"',
}); });
}, });
);
const IMessageConfigSchema = IMessageAccountSchemaBase.extend({ const IMessageConfigSchema = IMessageAccountSchemaBase.extend({
accounts: z.record(z.string(), IMessageAccountSchema.optional()).optional(), accounts: z.record(z.string(), IMessageAccountSchema.optional()).optional(),
@@ -696,24 +660,18 @@ const WhatsAppAccountSchema = z
.object({ .object({
emoji: z.string().optional(), emoji: z.string().optional(),
direct: z.boolean().optional().default(true), direct: z.boolean().optional().default(true),
group: z group: z.enum(["always", "mentions", "never"]).optional().default("mentions"),
.enum(["always", "mentions", "never"])
.optional()
.default("mentions"),
}) })
.optional(), .optional(),
}) })
.superRefine((value, ctx) => { .superRefine((value, ctx) => {
if (value.dmPolicy !== "open") return; if (value.dmPolicy !== "open") return;
const allow = (value.allowFrom ?? []) const allow = (value.allowFrom ?? []).map((v) => String(v).trim()).filter(Boolean);
.map((v) => String(v).trim())
.filter(Boolean);
if (allow.includes("*")) return; if (allow.includes("*")) return;
ctx.addIssue({ ctx.addIssue({
code: z.ZodIssueCode.custom, code: z.ZodIssueCode.custom,
path: ["allowFrom"], path: ["allowFrom"],
message: message: 'channels.whatsapp.accounts.*.dmPolicy="open" requires allowFrom to include "*"',
'channels.whatsapp.accounts.*.dmPolicy="open" requires allowFrom to include "*"',
}); });
}); });
@@ -755,18 +713,13 @@ const WhatsAppConfigSchema = z
.object({ .object({
emoji: z.string().optional(), emoji: z.string().optional(),
direct: z.boolean().optional().default(true), direct: z.boolean().optional().default(true),
group: z group: z.enum(["always", "mentions", "never"]).optional().default("mentions"),
.enum(["always", "mentions", "never"])
.optional()
.default("mentions"),
}) })
.optional(), .optional(),
}) })
.superRefine((value, ctx) => { .superRefine((value, ctx) => {
if (value.dmPolicy !== "open") return; if (value.dmPolicy !== "open") return;
const allow = (value.allowFrom ?? []) const allow = (value.allowFrom ?? []).map((v) => String(v).trim()).filter(Boolean);
.map((v) => String(v).trim())
.filter(Boolean);
if (allow.includes("*")) return; if (allow.includes("*")) return;
ctx.addIssue({ ctx.addIssue({
code: z.ZodIssueCode.custom, code: z.ZodIssueCode.custom,
@@ -816,11 +769,7 @@ const SessionSchema = z
.object({ .object({
channel: z.string().optional(), channel: z.string().optional(),
chatType: z chatType: z
.union([ .union([z.literal("direct"), z.literal("group"), z.literal("room")])
z.literal("direct"),
z.literal("group"),
z.literal("room"),
])
.optional(), .optional(),
keyPrefix: z.string().optional(), keyPrefix: z.string().optional(),
}) })
@@ -845,9 +794,7 @@ const MessagesSchema = z
groupChat: GroupChatSchema, groupChat: GroupChatSchema,
queue: QueueSchema, queue: QueueSchema,
ackReaction: z.string().optional(), ackReaction: z.string().optional(),
ackReactionScope: z ackReactionScope: z.enum(["group-mentions", "group-all", "direct", "all"]).optional(),
.enum(["group-mentions", "group-all", "direct", "all"])
.optional(),
removeAckAfterReply: z.boolean().optional(), removeAckAfterReply: z.boolean().optional(),
}) })
.optional(); .optional();
@@ -972,12 +919,7 @@ const ToolPolicySchema = z
.optional(); .optional();
const ToolProfileSchema = z const ToolProfileSchema = z
.union([ .union([z.literal("minimal"), z.literal("coding"), z.literal("messaging"), z.literal("full")])
z.literal("minimal"),
z.literal("coding"),
z.literal("messaging"),
z.literal("full"),
])
.optional(); .optional();
// Provider docking: allowlists keyed by provider id (no schema updates when adding providers). // Provider docking: allowlists keyed by provider id (no schema updates when adding providers).
@@ -987,18 +929,10 @@ const ElevatedAllowFromSchema = z
const AgentSandboxSchema = z const AgentSandboxSchema = z
.object({ .object({
mode: z mode: z.union([z.literal("off"), z.literal("non-main"), z.literal("all")]).optional(),
.union([z.literal("off"), z.literal("non-main"), z.literal("all")]) workspaceAccess: z.union([z.literal("none"), z.literal("ro"), z.literal("rw")]).optional(),
.optional(), sessionToolsVisibility: z.union([z.literal("spawned"), z.literal("all")]).optional(),
workspaceAccess: z scope: z.union([z.literal("session"), z.literal("agent"), z.literal("shared")]).optional(),
.union([z.literal("none"), z.literal("ro"), z.literal("rw")])
.optional(),
sessionToolsVisibility: z
.union([z.literal("spawned"), z.literal("all")])
.optional(),
scope: z
.union([z.literal("session"), z.literal("agent"), z.literal("shared")])
.optional(),
perSession: z.boolean().optional(), perSession: z.boolean().optional(),
workspaceRoot: z.string().optional(), workspaceRoot: z.string().optional(),
docker: SandboxDockerSchema, docker: SandboxDockerSchema,
@@ -1181,11 +1115,7 @@ const BindingsSchema = z
accountId: z.string().optional(), accountId: z.string().optional(),
peer: z peer: z
.object({ .object({
kind: z.union([ kind: z.union([z.literal("dm"), z.literal("group"), z.literal("channel")]),
z.literal("dm"),
z.literal("group"),
z.literal("channel"),
]),
id: z.string(), id: z.string(),
}) })
.optional(), .optional(),
@@ -1221,9 +1151,7 @@ const HookMappingSchema = z
}) })
.optional(), .optional(),
action: z.union([z.literal("wake"), z.literal("agent")]).optional(), action: z.union([z.literal("wake"), z.literal("agent")]).optional(),
wakeMode: z wakeMode: z.union([z.literal("now"), z.literal("next-heartbeat")]).optional(),
.union([z.literal("now"), z.literal("next-heartbeat")])
.optional(),
name: z.string().optional(), name: z.string().optional(),
sessionKey: z.string().optional(), sessionKey: z.string().optional(),
messageTemplate: z.string().optional(), messageTemplate: z.string().optional(),
@@ -1274,9 +1202,7 @@ const HooksGmailSchema = z
.optional(), .optional(),
tailscale: z tailscale: z
.object({ .object({
mode: z mode: z.union([z.literal("off"), z.literal("serve"), z.literal("funnel")]).optional(),
.union([z.literal("off"), z.literal("serve"), z.literal("funnel")])
.optional(),
path: z.string().optional(), path: z.string().optional(),
target: z.string().optional(), target: z.string().optional(),
}) })
@@ -1328,11 +1254,7 @@ const AgentDefaultsSchema = z
contextPruning: z contextPruning: z
.object({ .object({
mode: z mode: z
.union([ .union([z.literal("off"), z.literal("adaptive"), z.literal("aggressive")])
z.literal("off"),
z.literal("adaptive"),
z.literal("aggressive"),
])
.optional(), .optional(),
keepLastAssistants: z.number().int().nonnegative().optional(), keepLastAssistants: z.number().int().nonnegative().optional(),
softTrimRatio: z.number().min(0).max(1).optional(), softTrimRatio: z.number().min(0).max(1).optional(),
@@ -1361,9 +1283,7 @@ const AgentDefaultsSchema = z
.optional(), .optional(),
compaction: z compaction: z
.object({ .object({
mode: z mode: z.union([z.literal("default"), z.literal("safeguard")]).optional(),
.union([z.literal("default"), z.literal("safeguard")])
.optional(),
reserveTokensFloor: z.number().int().nonnegative().optional(), reserveTokensFloor: z.number().int().nonnegative().optional(),
memoryFlush: z memoryFlush: z
.object({ .object({
@@ -1387,12 +1307,8 @@ const AgentDefaultsSchema = z
.optional(), .optional(),
verboseDefault: z.union([z.literal("off"), z.literal("on")]).optional(), verboseDefault: z.union([z.literal("off"), z.literal("on")]).optional(),
elevatedDefault: z.union([z.literal("off"), z.literal("on")]).optional(), elevatedDefault: z.union([z.literal("off"), z.literal("on")]).optional(),
blockStreamingDefault: z blockStreamingDefault: z.union([z.literal("off"), z.literal("on")]).optional(),
.union([z.literal("off"), z.literal("on")]) blockStreamingBreak: z.union([z.literal("text_end"), z.literal("message_end")]).optional(),
.optional(),
blockStreamingBreak: z
.union([z.literal("text_end"), z.literal("message_end")])
.optional(),
blockStreamingChunk: BlockStreamingChunkSchema.optional(), blockStreamingChunk: BlockStreamingChunkSchema.optional(),
blockStreamingCoalesce: BlockStreamingCoalesceSchema.optional(), blockStreamingCoalesce: BlockStreamingCoalesceSchema.optional(),
humanDelay: HumanDelaySchema.optional(), humanDelay: HumanDelaySchema.optional(),
@@ -1426,22 +1342,10 @@ const AgentDefaultsSchema = z
.optional(), .optional(),
sandbox: z sandbox: z
.object({ .object({
mode: z mode: z.union([z.literal("off"), z.literal("non-main"), z.literal("all")]).optional(),
.union([z.literal("off"), z.literal("non-main"), z.literal("all")]) workspaceAccess: z.union([z.literal("none"), z.literal("ro"), z.literal("rw")]).optional(),
.optional(), sessionToolsVisibility: z.union([z.literal("spawned"), z.literal("all")]).optional(),
workspaceAccess: z scope: z.union([z.literal("session"), z.literal("agent"), z.literal("shared")]).optional(),
.union([z.literal("none"), z.literal("ro"), z.literal("rw")])
.optional(),
sessionToolsVisibility: z
.union([z.literal("spawned"), z.literal("all")])
.optional(),
scope: z
.union([
z.literal("session"),
z.literal("agent"),
z.literal("shared"),
])
.optional(),
perSession: z.boolean().optional(), perSession: z.boolean().optional(),
workspaceRoot: z.string().optional(), workspaceRoot: z.string().optional(),
docker: SandboxDockerSchema, docker: SandboxDockerSchema,

File diff suppressed because it is too large Load Diff

View File

@@ -17,15 +17,8 @@ import {
resolveChannelGroupPolicy, resolveChannelGroupPolicy,
resolveChannelGroupRequireMention, resolveChannelGroupRequireMention,
} from "../config/group-policy.js"; } from "../config/group-policy.js";
import { import { loadSessionStore, resolveStorePath } from "../config/sessions.js";
loadSessionStore,
resolveStorePath,
updateLastRoute,
} from "../config/sessions.js";
import { danger, logVerbose, shouldLogVerbose } from "../globals.js"; import { danger, logVerbose, shouldLogVerbose } from "../globals.js";
import { recordChannelActivity } from "../infra/channel-activity.js";
import { createDedupeCache } from "../infra/dedupe.js";
import { formatErrorMessage } from "../infra/errors.js";
import { enqueueSystemEvent } from "../infra/system-events.js"; import { enqueueSystemEvent } from "../infra/system-events.js";
import { getChildLogger } from "../logging.js"; import { getChildLogger } from "../logging.js";
import { resolveAgentRoute } from "../routing/resolve-route.js"; import { resolveAgentRoute } from "../routing/resolve-route.js";
@@ -47,12 +40,6 @@ import {
type TelegramUpdateKeyContext, type TelegramUpdateKeyContext,
} from "./bot-updates.js"; } from "./bot-updates.js";
import { resolveTelegramFetch } from "./fetch.js"; import { resolveTelegramFetch } from "./fetch.js";
import {
readTelegramAllowFromStore,
upsertTelegramPairingRequest,
} from "./pairing-store.js";
import { wasSentByBot } from "./sent-message-cache.js";
import { resolveTelegramVoiceSend } from "./voice.js";
export type TelegramBotOptions = { export type TelegramBotOptions = {
token: string; token: string;
@@ -334,23 +321,18 @@ export function createTelegramBot(opts: TelegramBotOptions) {
// Detect added reactions // Detect added reactions
const oldEmojis = new Set( const oldEmojis = new Set(
reaction.old_reaction reaction.old_reaction
.filter( .filter((r): r is { type: "emoji"; emoji: string } => r.type === "emoji")
(r): r is { type: "emoji"; emoji: string } => r.type === "emoji",
)
.map((r) => r.emoji), .map((r) => r.emoji),
); );
const addedReactions = reaction.new_reaction const addedReactions = reaction.new_reaction
.filter( .filter((r): r is { type: "emoji"; emoji: string } => r.type === "emoji")
(r): r is { type: "emoji"; emoji: string } => r.type === "emoji",
)
.filter((r) => !oldEmojis.has(r.emoji)); .filter((r) => !oldEmojis.has(r.emoji));
if (addedReactions.length === 0) return; if (addedReactions.length === 0) return;
// Build sender label // Build sender label
const senderName = user const senderName = user
? [user.first_name, user.last_name].filter(Boolean).join(" ").trim() || ? [user.first_name, user.last_name].filter(Boolean).join(" ").trim() || user.username
user.username
: undefined; : undefined;
const senderUsername = user?.username ? `@${user.username}` : undefined; const senderUsername = user?.username ? `@${user.username}` : undefined;
let senderLabel = senderName; let senderLabel = senderName;
@@ -373,11 +355,8 @@ export function createTelegramBot(opts: TelegramBotOptions) {
}); });
// Resolve agent route for session // Resolve agent route for session
const isGroup = const isGroup = reaction.chat.type === "group" || reaction.chat.type === "supergroup";
reaction.chat.type === "group" || reaction.chat.type === "supergroup"; const peerId = isGroup ? buildTelegramGroupPeerId(chatId, resolvedThreadId) : String(chatId);
const peerId = isGroup
? buildTelegramGroupPeerId(chatId, resolvedThreadId)
: String(chatId);
const route = resolveAgentRoute({ const route = resolveAgentRoute({
cfg, cfg,
channel: "telegram", channel: "telegram",
@@ -396,9 +375,7 @@ export function createTelegramBot(opts: TelegramBotOptions) {
logVerbose(`telegram: reaction event enqueued: ${text}`); logVerbose(`telegram: reaction event enqueued: ${text}`);
} }
} catch (err) { } catch (err) {
runtime.error?.( runtime.error?.(danger(`telegram reaction handler failed: ${String(err)}`));
danger(`telegram reaction handler failed: ${String(err)}`),
);
} }
}); });

View File

@@ -34,10 +34,7 @@ export function createTelegramRunnerOptions(cfg: ClawdbotConfig): RunOptions<unk
// Match grammY defaults // Match grammY defaults
timeout: 30, timeout: 30,
// Request reaction updates from Telegram // Request reaction updates from Telegram
allowed_updates: [ allowed_updates: ["message", "message_reaction"],
"message",
"message_reaction",
],
}, },
// Suppress grammY getUpdates stack traces; we log concise errors ourselves. // Suppress grammY getUpdates stack traces; we log concise errors ourselves.
silent: true, silent: true,

View File

@@ -24,8 +24,7 @@ export function resolveTelegramReactionLevel(params: {
cfg: params.cfg, cfg: params.cfg,
accountId: params.accountId, accountId: params.accountId,
}); });
const level = (account.config.reactionLevel ?? const level = (account.config.reactionLevel ?? "ack") as TelegramReactionLevel;
"ack") as TelegramReactionLevel;
switch (level) { switch (level) {
case "off": case "off":

View File

@@ -18,10 +18,7 @@ import { resolveTelegramAccount } from "./accounts.js";
import { resolveTelegramFetch } from "./fetch.js"; import { resolveTelegramFetch } from "./fetch.js";
import { markdownToTelegramHtml } from "./format.js"; import { markdownToTelegramHtml } from "./format.js";
import { recordSentMessage } from "./sent-message-cache.js"; import { recordSentMessage } from "./sent-message-cache.js";
import { import { parseTelegramTarget, stripTelegramInternalPrefixes } from "./targets.js";
parseTelegramTarget,
stripTelegramInternalPrefixes,
} from "./targets.js";
import { resolveTelegramVoiceSend } from "./voice.js"; import { resolveTelegramVoiceSend } from "./voice.js";
type TelegramSendOpts = { type TelegramSendOpts = {

View File

@@ -1,9 +1,5 @@
import { afterEach, describe, expect, it } from "vitest"; import { afterEach, describe, expect, it } from "vitest";
import { import { clearSentMessageCache, recordSentMessage, wasSentByBot } from "./sent-message-cache.js";
clearSentMessageCache,
recordSentMessage,
wasSentByBot,
} from "./sent-message-cache.js";
describe("sent-message-cache", () => { describe("sent-message-cache", () => {
afterEach(() => { afterEach(() => {

View File

@@ -29,10 +29,7 @@ function cleanupExpired(entry: CacheEntry): void {
/** /**
* Record a message ID as sent by the bot. * Record a message ID as sent by the bot.
*/ */
export function recordSentMessage( export function recordSentMessage(chatId: number | string, messageId: number): void {
chatId: number | string,
messageId: number,
): void {
const key = getChatKey(chatId); const key = getChatKey(chatId);
let entry = sentMessages.get(key); let entry = sentMessages.get(key);
if (!entry) { if (!entry) {
@@ -50,10 +47,7 @@ export function recordSentMessage(
/** /**
* Check if a message was sent by the bot. * Check if a message was sent by the bot.
*/ */
export function wasSentByBot( export function wasSentByBot(chatId: number | string, messageId: number): boolean {
chatId: number | string,
messageId: number,
): boolean {
const key = getChatKey(chatId); const key = getChatKey(chatId);
const entry = sentMessages.get(key); const entry = sentMessages.get(key);
if (!entry) return false; if (!entry) return false;

View File

@@ -63,10 +63,7 @@ export async function startTelegramWebhook(opts: {
await bot.api.setWebhook(publicUrl, { await bot.api.setWebhook(publicUrl, {
secret_token: opts.secret, secret_token: opts.secret,
allowed_updates: [ allowed_updates: ["message", "message_reaction"],
"message",
"message_reaction",
],
}); });
await new Promise<void>((resolve) => server.listen(port, host, resolve)); await new Promise<void>((resolve) => server.listen(port, host, resolve));