Format: apply oxfmt fixes

This commit is contained in:
George Pickett
2026-01-14 17:10:16 -08:00
committed by Peter Steinberger
parent 8c1e6a82b2
commit 232c512502
14 changed files with 202 additions and 234 deletions

View File

@@ -2,82 +2,77 @@ import { completeSimple, getModel } from "@mariozechner/pi-ai";
import { describe, expect, it } from "vitest"; import { describe, expect, it } from "vitest";
const GEMINI_KEY = process.env.GEMINI_API_KEY ?? ""; const GEMINI_KEY = process.env.GEMINI_API_KEY ?? "";
const LIVE = const LIVE = process.env.GEMINI_LIVE_TEST === "1" || process.env.LIVE === "1";
process.env.GEMINI_LIVE_TEST === "1" || process.env.LIVE === "1";
const describeLive = LIVE && GEMINI_KEY ? describe : describe.skip; const describeLive = LIVE && GEMINI_KEY ? describe : describe.skip;
describeLive("gemini live switch", () => { describeLive("gemini live switch", () => {
it( it("handles unsigned tool calls from Antigravity when switching to Gemini 3", async () => {
"handles unsigned tool calls from Antigravity when switching to Gemini 3", const now = Date.now();
async () => { const model = getModel("google", "gemini-3-pro-preview");
const now = Date.now();
const model = getModel("google", "gemini-3-pro-preview");
const res = await completeSimple( const res = await completeSimple(
model, model,
{ {
messages: [ messages: [
{ {
role: "user", role: "user",
content: "Reply with ok.", content: "Reply with ok.",
timestamp: now, timestamp: now,
}, },
{ {
role: "assistant", role: "assistant",
content: [ content: [
{ {
type: "toolCall", type: "toolCall",
id: "call_1", id: "call_1",
name: "bash", name: "bash",
arguments: { command: "ls -la" }, arguments: { command: "ls -la" },
// No thoughtSignature: simulates Claude via Antigravity. // No thoughtSignature: simulates Claude via Antigravity.
}, },
], ],
api: "google-gemini-cli", api: "google-gemini-cli",
provider: "google-antigravity", provider: "google-antigravity",
model: "claude-sonnet-4-20250514", model: "claude-sonnet-4-20250514",
usage: { usage: {
input: 0,
output: 0,
cacheRead: 0,
cacheWrite: 0,
totalTokens: 0,
cost: {
input: 0, input: 0,
output: 0, output: 0,
cacheRead: 0, cacheRead: 0,
cacheWrite: 0, cacheWrite: 0,
totalTokens: 0, total: 0,
cost: {
input: 0,
output: 0,
cacheRead: 0,
cacheWrite: 0,
total: 0,
},
},
stopReason: "stop",
timestamp: now,
},
],
tools: [
{
name: "bash",
description: "Run shell command",
parameters: {
type: "object",
properties: {
command: { type: "string" },
},
required: ["command"],
}, },
}, },
], stopReason: "stop",
}, timestamp: now,
{ },
apiKey: GEMINI_KEY, ],
reasoning: "low", tools: [
maxTokens: 128, {
}, name: "bash",
); description: "Run shell command",
parameters: {
type: "object",
properties: {
command: { type: "string" },
},
required: ["command"],
},
},
],
},
{
apiKey: GEMINI_KEY,
reasoning: "low",
maxTokens: 128,
},
);
expect(res.stopReason).not.toBe("error"); expect(res.stopReason).not.toBe("error");
}, }, 20000);
20000,
);
}); });

View File

@@ -116,7 +116,12 @@ export function resolveCommandAuthorization(params: {
const ownerCandidates = allowAll ? [] : allowFromList.filter((entry) => entry !== "*"); const ownerCandidates = allowAll ? [] : allowFromList.filter((entry) => entry !== "*");
if (!allowAll && ownerCandidates.length === 0 && to) { if (!allowAll && ownerCandidates.length === 0 && to) {
const normalizedTo = normalizeAllowFromEntry({ dock, cfg, accountId: ctx.AccountId, value: to }); const normalizedTo = normalizeAllowFromEntry({
dock,
cfg,
accountId: ctx.AccountId,
value: to,
});
if (normalizedTo) ownerCandidates.push(normalizedTo); if (normalizedTo) ownerCandidates.push(normalizedTo);
} }
const ownerList = ownerCandidates; const ownerList = ownerCandidates;

View File

@@ -26,7 +26,7 @@ export function buildThreadingToolContext(params: {
const dock = getChannelDock(provider); const dock = getChannelDock(provider);
if (!dock?.threading?.buildToolContext) return {}; if (!dock?.threading?.buildToolContext) return {};
// WhatsApp context isolation keys off conversation id, not the bot's own number. // WhatsApp context isolation keys off conversation id, not the bot's own number.
const threadingTo = provider === "whatsapp" ? sessionCtx.From ?? sessionCtx.To : sessionCtx.To; const threadingTo = provider === "whatsapp" ? (sessionCtx.From ?? sessionCtx.To) : sessionCtx.To;
return ( return (
dock.threading.buildToolContext({ dock.threading.buildToolContext({
cfg: config, cfg: config,

View File

@@ -9,9 +9,7 @@ import { initSessionState } from "./session.js";
describe("initSessionState reset triggers in WhatsApp groups", () => { describe("initSessionState reset triggers in WhatsApp groups", () => {
async function createStorePath(prefix: string): Promise<string> { async function createStorePath(prefix: string): Promise<string> {
const root = await fs.mkdtemp( const root = await fs.mkdtemp(path.join(os.tmpdir(), prefix));
path.join(os.tmpdir(), prefix),
);
return path.join(root, "sessions.json"); return path.join(root, "sessions.json");
} }

View File

@@ -24,10 +24,7 @@ import {
} from "./message-utils.js"; } from "./message-utils.js";
import { buildDirectLabel, buildGuildLabel, resolveReplyContext } from "./reply-context.js"; import { buildDirectLabel, buildGuildLabel, resolveReplyContext } from "./reply-context.js";
import { deliverDiscordReply } from "./reply-delivery.js"; import { deliverDiscordReply } from "./reply-delivery.js";
import { import { resolveDiscordAutoThreadReplyPlan, resolveDiscordThreadStarter } from "./threading.js";
resolveDiscordAutoThreadReplyPlan,
resolveDiscordThreadStarter,
} from "./threading.js";
import { sendTyping } from "./typing.js"; import { sendTyping } from "./typing.js";
export async function processDiscordMessage(ctx: DiscordMessagePreflightContext) { export async function processDiscordMessage(ctx: DiscordMessagePreflightContext) {
@@ -191,72 +188,72 @@ export async function processDiscordMessage(ctx: DiscordMessagePreflightContext)
peer: { kind: "channel", id: threadParentId }, peer: { kind: "channel", id: threadParentId },
}); });
} }
} }
const mediaPayload = buildDiscordMediaPayload(mediaList); const mediaPayload = buildDiscordMediaPayload(mediaList);
const threadKeys = resolveThreadSessionKeys({ const threadKeys = resolveThreadSessionKeys({
baseSessionKey, baseSessionKey,
threadId: threadChannel ? message.channelId : undefined, threadId: threadChannel ? message.channelId : undefined,
parentSessionKey, parentSessionKey,
useSuffix: false, useSuffix: false,
}); });
const replyPlan = await resolveDiscordAutoThreadReplyPlan({ const replyPlan = await resolveDiscordAutoThreadReplyPlan({
client, client,
message, message,
isGuildMessage, isGuildMessage,
channelConfig, channelConfig,
threadChannel, threadChannel,
baseText: baseText ?? "", baseText: baseText ?? "",
combinedBody, combinedBody,
replyToMode, replyToMode,
agentId: route.agentId, agentId: route.agentId,
channel: route.channel, channel: route.channel,
}); });
const deliverTarget = replyPlan.deliverTarget; const deliverTarget = replyPlan.deliverTarget;
const replyTarget = replyPlan.replyTarget; const replyTarget = replyPlan.replyTarget;
const replyReference = replyPlan.replyReference; const replyReference = replyPlan.replyReference;
const autoThreadContext = replyPlan.autoThreadContext; const autoThreadContext = replyPlan.autoThreadContext;
const effectiveFrom = isDirectMessage const effectiveFrom = isDirectMessage
? `discord:${author.id}` ? `discord:${author.id}`
: (autoThreadContext?.From ?? `group:${message.channelId}`); : (autoThreadContext?.From ?? `group:${message.channelId}`);
const effectiveTo = autoThreadContext?.To ?? replyTarget; const effectiveTo = autoThreadContext?.To ?? replyTarget;
if (!effectiveTo) { if (!effectiveTo) {
runtime.error?.(danger("discord: missing reply target")); runtime.error?.(danger("discord: missing reply target"));
return; return;
} }
const ctxPayload = { const ctxPayload = {
Body: combinedBody, Body: combinedBody,
RawBody: baseText, RawBody: baseText,
CommandBody: baseText, CommandBody: baseText,
From: effectiveFrom, From: effectiveFrom,
To: effectiveTo, To: effectiveTo,
SessionKey: autoThreadContext?.SessionKey ?? threadKeys.sessionKey, SessionKey: autoThreadContext?.SessionKey ?? threadKeys.sessionKey,
AccountId: route.accountId, AccountId: route.accountId,
ChatType: isDirectMessage ? "direct" : "group", ChatType: isDirectMessage ? "direct" : "group",
SenderName: data.member?.nickname ?? author.globalName ?? author.username, SenderName: data.member?.nickname ?? author.globalName ?? author.username,
SenderId: author.id, SenderId: author.id,
SenderUsername: author.username, SenderUsername: author.username,
SenderTag: formatDiscordUserTag(author), SenderTag: formatDiscordUserTag(author),
GroupSubject: groupSubject, GroupSubject: groupSubject,
GroupRoom: groupRoom, GroupRoom: groupRoom,
GroupSystemPrompt: isGuildMessage ? groupSystemPrompt : undefined, GroupSystemPrompt: isGuildMessage ? groupSystemPrompt : undefined,
GroupSpace: isGuildMessage ? (guildInfo?.id ?? guildSlug) || undefined : undefined, GroupSpace: isGuildMessage ? (guildInfo?.id ?? guildSlug) || undefined : undefined,
Provider: "discord" as const, Provider: "discord" as const,
Surface: "discord" as const, Surface: "discord" as const,
WasMentioned: effectiveWasMentioned, WasMentioned: effectiveWasMentioned,
MessageSid: message.id, MessageSid: message.id,
ParentSessionKey: autoThreadContext?.ParentSessionKey ?? threadKeys.parentSessionKey, ParentSessionKey: autoThreadContext?.ParentSessionKey ?? threadKeys.parentSessionKey,
ThreadStarterBody: threadStarterBody, ThreadStarterBody: threadStarterBody,
ThreadLabel: threadLabel, ThreadLabel: threadLabel,
Timestamp: resolveTimestampMs(message.timestamp), Timestamp: resolveTimestampMs(message.timestamp),
...mediaPayload, ...mediaPayload,
CommandAuthorized: commandAuthorized, CommandAuthorized: commandAuthorized,
CommandSource: "text" as const, CommandSource: "text" as const,
// Originating channel for reply routing. // Originating channel for reply routing.
OriginatingChannel: "discord" as const, OriginatingChannel: "discord" as const,
OriginatingTo: autoThreadContext?.OriginatingTo ?? replyTarget, OriginatingTo: autoThreadContext?.OriginatingTo ?? replyTarget,
}; };
if (isDirectMessage) { if (isDirectMessage) {
const sessionCfg = cfg.session; const sessionCfg = cfg.session;
@@ -272,20 +269,20 @@ export async function processDiscordMessage(ctx: DiscordMessagePreflightContext)
}); });
} }
if (shouldLogVerbose()) { if (shouldLogVerbose()) {
const preview = truncateUtf16Safe(combinedBody, 200).replace(/\n/g, "\\n"); const preview = truncateUtf16Safe(combinedBody, 200).replace(/\n/g, "\\n");
logVerbose( logVerbose(
`discord inbound: channel=${message.channelId} deliver=${deliverTarget} from=${ctxPayload.From} preview="${preview}"`, `discord inbound: channel=${message.channelId} deliver=${deliverTarget} from=${ctxPayload.From} preview="${preview}"`,
); );
} }
let didSendReply = false; let didSendReply = false;
const typingChannelId = deliverTarget.startsWith("channel:") const typingChannelId = deliverTarget.startsWith("channel:")
? deliverTarget.slice("channel:".length) ? deliverTarget.slice("channel:".length)
: message.channelId; : message.channelId;
const { dispatcher, replyOptions, markDispatchIdle } = createReplyDispatcherWithTyping({ const { dispatcher, replyOptions, markDispatchIdle } = createReplyDispatcherWithTyping({
responsePrefix: resolveEffectiveMessagesConfig(cfg, route.agentId).responsePrefix, responsePrefix: resolveEffectiveMessagesConfig(cfg, route.agentId).responsePrefix,
humanDelay: resolveHumanDelayConfig(cfg, route.agentId), humanDelay: resolveHumanDelayConfig(cfg, route.agentId),
deliver: async (payload: ReplyPayload) => { deliver: async (payload: ReplyPayload) => {
const replyToId = replyReference.use(); const replyToId = replyReference.use();
await deliverDiscordReply({ await deliverDiscordReply({
@@ -302,11 +299,11 @@ export async function processDiscordMessage(ctx: DiscordMessagePreflightContext)
didSendReply = true; didSendReply = true;
replyReference.markSent(); replyReference.markSent();
}, },
onError: (err, info) => { onError: (err, info) => {
runtime.error?.(danger(`discord ${info.kind} reply failed: ${String(err)}`)); runtime.error?.(danger(`discord ${info.kind} reply failed: ${String(err)}`));
}, },
onReplyStart: () => sendTyping({ client, channelId: typingChannelId }), onReplyStart: () => sendTyping({ client, channelId: typingChannelId }),
}); });
const { queuedFinal, counts } = await dispatchReplyFromConfig({ const { queuedFinal, counts } = await dispatchReplyFromConfig({
ctx: ctxPayload, ctx: ctxPayload,

View File

@@ -93,9 +93,14 @@ describe("resolveDiscordAutoThreadReplyPlan", () => {
} as unknown as Client; } as unknown as Client;
const plan = await resolveDiscordAutoThreadReplyPlan({ const plan = await resolveDiscordAutoThreadReplyPlan({
client, client,
message: { id: "m1", channelId: "parent" } as unknown as import("./listeners.js").DiscordMessageEvent["message"], message: {
id: "m1",
channelId: "parent",
} as unknown as import("./listeners.js").DiscordMessageEvent["message"],
isGuildMessage: true, isGuildMessage: true,
channelConfig: { autoThread: true } as unknown as import("./allow-list.js").DiscordChannelConfigResolved, channelConfig: {
autoThread: true,
} as unknown as import("./allow-list.js").DiscordChannelConfigResolved,
threadChannel: null, threadChannel: null,
baseText: "hello", baseText: "hello",
combinedBody: "hello", combinedBody: "hello",
@@ -118,9 +123,14 @@ describe("resolveDiscordAutoThreadReplyPlan", () => {
const client = { rest: { post: async () => ({ id: "thread" }) } } as unknown as Client; const client = { rest: { post: async () => ({ id: "thread" }) } } as unknown as Client;
const plan = await resolveDiscordAutoThreadReplyPlan({ const plan = await resolveDiscordAutoThreadReplyPlan({
client, client,
message: { id: "m1", channelId: "parent" } as unknown as import("./listeners.js").DiscordMessageEvent["message"], message: {
id: "m1",
channelId: "parent",
} as unknown as import("./listeners.js").DiscordMessageEvent["message"],
isGuildMessage: true, isGuildMessage: true,
channelConfig: { autoThread: false } as unknown as import("./allow-list.js").DiscordChannelConfigResolved, channelConfig: {
autoThread: false,
} as unknown as import("./allow-list.js").DiscordChannelConfigResolved,
threadChannel: null, threadChannel: null,
baseText: "hello", baseText: "hello",
combinedBody: "hello", combinedBody: "hello",

View File

@@ -86,9 +86,7 @@ describe("deliverOutboundPayloads", () => {
}); });
it("chunks Signal markdown using the format-first chunker", async () => { it("chunks Signal markdown using the format-first chunker", async () => {
const sendSignal = vi const sendSignal = vi.fn().mockResolvedValue({ messageId: "s1", timestamp: 123 });
.fn()
.mockResolvedValue({ messageId: "s1", timestamp: 123 });
const cfg: ClawdbotConfig = { const cfg: ClawdbotConfig = {
channels: { signal: { textChunkLimit: 20 } }, channels: { signal: { textChunkLimit: 20 } },
}; };

View File

@@ -25,13 +25,7 @@ type MarkdownToken = {
attrGet?: (name: string) => string | null; attrGet?: (name: string) => string | null;
}; };
export type MarkdownStyle = export type MarkdownStyle = "bold" | "italic" | "strikethrough" | "code" | "code_block" | "spoiler";
| "bold"
| "italic"
| "strikethrough"
| "code"
| "code_block"
| "spoiler";
export type MarkdownStyleSpan = { export type MarkdownStyleSpan = {
start: number; start: number;
@@ -414,11 +408,7 @@ function sliceStyleSpans(
return mergeStyleSpans(sliced); return mergeStyleSpans(sliced);
} }
function sliceLinkSpans( function sliceLinkSpans(spans: MarkdownLinkSpan[], start: number, end: number): MarkdownLinkSpan[] {
spans: MarkdownLinkSpan[],
start: number,
end: number,
): MarkdownLinkSpan[] {
if (spans.length === 0) return []; if (spans.length === 0) return [];
const sliced: MarkdownLinkSpan[] = []; const sliced: MarkdownLinkSpan[] = [];
for (const span of spans) { for (const span of spans) {
@@ -465,7 +455,8 @@ export function markdownToIR(markdown: string, options: MarkdownParseOptions = {
if (span.end > codeBlockEnd) codeBlockEnd = span.end; if (span.end > codeBlockEnd) codeBlockEnd = span.end;
} }
const finalLength = Math.max(trimmedLength, codeBlockEnd); const finalLength = Math.max(trimmedLength, codeBlockEnd);
const finalText = finalLength === state.text.length ? state.text : state.text.slice(0, finalLength); const finalText =
finalLength === state.text.length ? state.text : state.text.slice(0, finalLength);
return { return {
text: finalText, text: finalText,

View File

@@ -46,9 +46,7 @@ export function renderMarkdownWithMarkers(ir: MarkdownIR, options: RenderOptions
if (!text) return ""; if (!text) return "";
const styleMarkers = options.styleMarkers; const styleMarkers = options.styleMarkers;
const styled = sortStyleSpans( const styled = sortStyleSpans(ir.styles.filter((span) => Boolean(styleMarkers[span.style])));
ir.styles.filter((span) => Boolean(styleMarkers[span.style])),
);
const boundaries = new Set<number>(); const boundaries = new Set<number>();
boundaries.add(0); boundaries.add(0);

View File

@@ -16,13 +16,9 @@ describe("markdownToSignalText", () => {
}); });
it("renders links as label plus url when needed", () => { it("renders links as label plus url when needed", () => {
const res = markdownToSignalText( const res = markdownToSignalText("see [docs](https://example.com) and https://example.com");
"see [docs](https://example.com) and https://example.com",
);
expect(res.text).toBe( expect(res.text).toBe("see docs (https://example.com) and https://example.com");
"see docs (https://example.com) and https://example.com",
);
expect(res.styles).toEqual([]); expect(res.styles).toEqual([]);
}); });
@@ -34,18 +30,14 @@ describe("markdownToSignalText", () => {
}); });
it("renders fenced code blocks with monospaced styles", () => { it("renders fenced code blocks with monospaced styles", () => {
const res = markdownToSignalText( const res = markdownToSignalText("before\n\n```\nconst x = 1;\n```\n\nafter");
"before\n\n```\nconst x = 1;\n```\n\nafter",
);
const prefix = "before\n\n"; const prefix = "before\n\n";
const code = "const x = 1;\n"; const code = "const x = 1;\n";
const suffix = "\nafter"; const suffix = "\nafter";
expect(res.text).toBe(`${prefix}${code}${suffix}`); expect(res.text).toBe(`${prefix}${code}${suffix}`);
expect(res.styles).toEqual([ expect(res.styles).toEqual([{ start: prefix.length, length: code.length, style: "MONOSPACE" }]);
{ start: prefix.length, length: code.length, style: "MONOSPACE" },
]);
}); });
it("renders lists without extra block markup", () => { it("renders lists without extra block markup", () => {
@@ -60,8 +52,6 @@ describe("markdownToSignalText", () => {
const prefix = "😀 "; const prefix = "😀 ";
expect(res.text).toBe(`${prefix}bold`); expect(res.text).toBe(`${prefix}bold`);
expect(res.styles).toEqual([ expect(res.styles).toEqual([{ start: prefix.length, length: 4, style: "BOLD" }]);
{ start: prefix.length, length: 4, style: "BOLD" },
]);
}); });
}); });

View File

@@ -1,11 +1,11 @@
import { chunkMarkdownIR, markdownToIR, type MarkdownIR, type MarkdownStyle } from "../markdown/ir.js"; import {
chunkMarkdownIR,
markdownToIR,
type MarkdownIR,
type MarkdownStyle,
} from "../markdown/ir.js";
type SignalTextStyle = type SignalTextStyle = "BOLD" | "ITALIC" | "STRIKETHROUGH" | "MONOSPACE" | "SPOILER";
| "BOLD"
| "ITALIC"
| "STRIKETHROUGH"
| "MONOSPACE"
| "SPOILER";
export type SignalTextStyleRange = { export type SignalTextStyleRange = {
start: number; start: number;
@@ -57,11 +57,7 @@ function mergeStyles(styles: SignalTextStyleRange[]): SignalTextStyleRange[] {
const merged: SignalTextStyleRange[] = []; const merged: SignalTextStyleRange[] = [];
for (const style of sorted) { for (const style of sorted) {
const prev = merged[merged.length - 1]; const prev = merged[merged.length - 1];
if ( if (prev && prev.style === style.style && style.start <= prev.start + prev.length) {
prev &&
prev.style === style.style &&
style.start <= prev.start + prev.length
) {
const prevEnd = prev.start + prev.length; const prevEnd = prev.start + prev.length;
const nextEnd = Math.max(prevEnd, style.start + style.length); const nextEnd = Math.max(prevEnd, style.start + style.length);
prev.length = nextEnd - prev.start; prev.length = nextEnd - prev.start;
@@ -73,10 +69,7 @@ function mergeStyles(styles: SignalTextStyleRange[]): SignalTextStyleRange[] {
return merged; return merged;
} }
function clampStyles( function clampStyles(styles: SignalTextStyleRange[], maxLength: number): SignalTextStyleRange[] {
styles: SignalTextStyleRange[],
maxLength: number,
): SignalTextStyleRange[] {
const clamped: SignalTextStyleRange[] = []; const clamped: SignalTextStyleRange[] = [];
for (const style of styles) { for (const style of styles) {
const start = Math.max(0, Math.min(style.start, maxLength)); const start = Math.max(0, Math.min(style.start, maxLength));
@@ -205,10 +198,7 @@ export function markdownToSignalText(markdown: string): SignalFormattedText {
return renderSignalText(ir); return renderSignalText(ir);
} }
export function markdownToSignalTextChunks( export function markdownToSignalTextChunks(markdown: string, limit: number): SignalFormattedText[] {
markdown: string,
limit: number,
): SignalFormattedText[] {
const ir = markdownToIR(markdown ?? "", { const ir = markdownToIR(markdown ?? "", {
linkify: true, linkify: true,
enableSpoilers: true, enableSpoilers: true,

View File

@@ -1,4 +1,9 @@
import { chunkMarkdownIR, markdownToIR, type MarkdownLinkSpan, type MarkdownIR } from "../markdown/ir.js"; import {
chunkMarkdownIR,
markdownToIR,
type MarkdownLinkSpan,
type MarkdownIR,
} from "../markdown/ir.js";
import { renderMarkdownWithMarkers } from "../markdown/render.js"; import { renderMarkdownWithMarkers } from "../markdown/render.js";
export type TelegramFormattedChunk = { export type TelegramFormattedChunk = {
@@ -50,7 +55,10 @@ export function markdownToTelegramHtml(markdown: string): string {
return renderTelegramHtml(ir); return renderTelegramHtml(ir);
} }
export function markdownToTelegramChunks(markdown: string, limit: number): TelegramFormattedChunk[] { export function markdownToTelegramChunks(
markdown: string,
limit: number,
): TelegramFormattedChunk[] {
const ir = markdownToIR(markdown ?? "", { const ir = markdownToIR(markdown ?? "", {
linkify: true, linkify: true,
headingStyle: "none", headingStyle: "none",

View File

@@ -432,9 +432,7 @@ export async function deleteMessageTelegram(
verbose: opts.verbose, verbose: opts.verbose,
}); });
await request(() => api.deleteMessage(chatId, messageId), "deleteMessage"); await request(() => api.deleteMessage(chatId, messageId), "deleteMessage");
logVerbose( logVerbose(`[telegram] Deleted message ${messageId} from chat ${chatId}`);
`[telegram] Deleted message ${messageId} from chat ${chatId}`,
);
return { ok: true }; return { ok: true };
} }

View File

@@ -1,10 +1,6 @@
import { describe, expect, it } from "vitest"; import { describe, expect, it } from "vitest";
import { import { isWhatsAppGroupJid, isWhatsAppUserTarget, normalizeWhatsAppTarget } from "./normalize.js";
isWhatsAppGroupJid,
isWhatsAppUserTarget,
normalizeWhatsAppTarget,
} from "./normalize.js";
describe("normalizeWhatsAppTarget", () => { describe("normalizeWhatsAppTarget", () => {
it("preserves group JIDs", () => { it("preserves group JIDs", () => {
@@ -31,16 +27,10 @@ describe("normalizeWhatsAppTarget", () => {
it("normalizes user JIDs with device suffix to E.164", () => { it("normalizes user JIDs with device suffix to E.164", () => {
// This is the bug fix: JIDs like "41796666864:0@s.whatsapp.net" should // This is the bug fix: JIDs like "41796666864:0@s.whatsapp.net" should
// normalize to "+41796666864", not "+417966668640" (extra digit from ":0") // normalize to "+41796666864", not "+417966668640" (extra digit from ":0")
expect(normalizeWhatsAppTarget("41796666864:0@s.whatsapp.net")).toBe( expect(normalizeWhatsAppTarget("41796666864:0@s.whatsapp.net")).toBe("+41796666864");
"+41796666864", expect(normalizeWhatsAppTarget("1234567890:123@s.whatsapp.net")).toBe("+1234567890");
);
expect(normalizeWhatsAppTarget("1234567890:123@s.whatsapp.net")).toBe(
"+1234567890",
);
// Without device suffix still works // Without device suffix still works
expect(normalizeWhatsAppTarget("41796666864@s.whatsapp.net")).toBe( expect(normalizeWhatsAppTarget("41796666864@s.whatsapp.net")).toBe("+41796666864");
"+41796666864",
);
}); });
it("normalizes LID JIDs to E.164", () => { it("normalizes LID JIDs to E.164", () => {