chore: format + regenerate protocol

This commit is contained in:
Peter Steinberger
2026-01-17 03:40:49 +00:00
parent 09bed2ccde
commit a82217a5f3
20 changed files with 79 additions and 74 deletions

View File

@@ -357,7 +357,7 @@ public struct SendParams: Codable, Sendable {
gifplayback: Bool?,
channel: String?,
accountid: String?,
sessionkey: String? = nil,
sessionkey: String?,
idempotencykey: String
) {
self.to = to
@@ -431,6 +431,7 @@ public struct AgentParams: Codable, Sendable {
public let deliver: Bool?
public let attachments: [AnyCodable]?
public let channel: String?
public let accountid: String?
public let timeout: Int?
public let lane: String?
public let extrasystemprompt: String?
@@ -447,6 +448,7 @@ public struct AgentParams: Codable, Sendable {
deliver: Bool?,
attachments: [AnyCodable]?,
channel: String?,
accountid: String?,
timeout: Int?,
lane: String?,
extrasystemprompt: String?,
@@ -462,6 +464,7 @@ public struct AgentParams: Codable, Sendable {
self.deliver = deliver
self.attachments = attachments
self.channel = channel
self.accountid = accountid
self.timeout = timeout
self.lane = lane
self.extrasystemprompt = extrasystemprompt
@@ -478,6 +481,7 @@ public struct AgentParams: Codable, Sendable {
case deliver
case attachments
case channel
case accountid = "accountId"
case timeout
case lane
case extrasystemprompt = "extraSystemPrompt"

View File

@@ -37,5 +37,7 @@ export function channelTargetSchema(options?: { description?: string }) {
}
export function channelTargetsSchema(options?: { description?: string }) {
return Type.Array(channelTargetSchema({ description: options?.description ?? CHANNEL_TARGETS_DESCRIPTION }));
return Type.Array(
channelTargetSchema({ description: options?.description ?? CHANNEL_TARGETS_DESCRIPTION }),
);
}

View File

@@ -16,10 +16,7 @@ import {
} from "../auto-reply/reply/queue.js";
import { callGateway } from "../gateway/call.js";
import { defaultRuntime } from "../runtime.js";
import {
type DeliveryContext,
normalizeDeliveryContext,
} from "../utils/delivery-context.js";
import { type DeliveryContext, normalizeDeliveryContext } from "../utils/delivery-context.js";
import { isEmbeddedPiRunActive, queueEmbeddedPiMessage } from "./pi-embedded.js";
import { readLatestAssistantReply } from "./tools/agent-step.js";

View File

@@ -11,7 +11,9 @@ describe("formatInboundBodyWithSenderMeta", () => {
it("appends a sender meta line for non-direct messages", () => {
const ctx: MsgContext = { ChatType: "group", SenderName: "Alice", SenderId: "A1" };
expect(formatInboundBodyWithSenderMeta({ ctx, body: "[X] hi" })).toBe("[X] hi\n[from: Alice (A1)]");
expect(formatInboundBodyWithSenderMeta({ ctx, body: "[X] hi" })).toBe(
"[X] hi\n[from: Alice (A1)]",
);
});
it("prefers SenderE164 in the label when present", () => {
@@ -21,7 +23,9 @@ describe("formatInboundBodyWithSenderMeta", () => {
SenderId: "bob@s.whatsapp.net",
SenderE164: "+222",
};
expect(formatInboundBodyWithSenderMeta({ ctx, body: "[X] hi" })).toBe("[X] hi\n[from: Bob (+222)]");
expect(formatInboundBodyWithSenderMeta({ ctx, body: "[X] hi" })).toBe(
"[X] hi\n[from: Bob (+222)]",
);
});
it("preserves escaped newline style when body uses literal \\\\n", () => {
@@ -38,4 +42,3 @@ describe("formatInboundBodyWithSenderMeta", () => {
);
});
});

View File

@@ -1,9 +1,6 @@
import type { MsgContext } from "../templating.js";
export function formatInboundBodyWithSenderMeta(params: {
body: string;
ctx: MsgContext;
}): string {
export function formatInboundBodyWithSenderMeta(params: { body: string; ctx: MsgContext }): string {
const body = params.body;
if (!body.trim()) return body;
const chatType = params.ctx.ChatType?.trim().toLowerCase();

View File

@@ -49,4 +49,3 @@ describe("initSessionState sender meta", () => {
expect(result.sessionCtx.BodyStripped).toBe("[WhatsApp +1] ping");
});
});

View File

@@ -234,8 +234,7 @@ export const discordPlugin: ChannelPlugin<ResolvedDiscordAccount> = {
}
}
const filtered = q ? rows.filter((row) => row.name?.toLowerCase().includes(q)) : rows;
const limited =
typeof limit === "number" && limit > 0 ? filtered.slice(0, limit) : filtered;
const limited = typeof limit === "number" && limit > 0 ? filtered.slice(0, limit) : filtered;
return limited;
},
},

View File

@@ -25,15 +25,9 @@ export function createMessageCliHelpers(
.option("--verbose", "Verbose logging", false);
const withMessageTarget = (command: Command) =>
command.option(
"-t, --to <dest>",
CHANNEL_TARGET_DESCRIPTION,
);
command.option("-t, --to <dest>", CHANNEL_TARGET_DESCRIPTION);
const withRequiredMessageTarget = (command: Command) =>
command.requiredOption(
"-t, --to <dest>",
CHANNEL_TARGET_DESCRIPTION,
);
command.requiredOption("-t, --to <dest>", CHANNEL_TARGET_DESCRIPTION);
const runMessageAction = async (action: string, opts: Record<string, unknown>) => {
setVerbose(Boolean(opts.verbose));

View File

@@ -7,10 +7,7 @@ export function registerMessageBroadcastCommand(message: Command, helpers: Messa
.withMessageBase(
message.command("broadcast").description("Broadcast a message to multiple targets"),
)
.requiredOption(
"--targets <target...>",
CHANNEL_TARGETS_DESCRIPTION,
)
.requiredOption("--targets <target...>", CHANNEL_TARGETS_DESCRIPTION)
.option("--message <text>", "Message to send")
.option("--media <url>", "Media URL")
.action(async (options: Record<string, unknown>) => {

View File

@@ -275,8 +275,7 @@ const FIELD_HELP: Record<string, string> = {
'Text prefix for cross-context markers (supports "{channel}").',
"tools.message.crossContext.marker.suffix":
'Text suffix for cross-context markers (supports "{channel}").',
"tools.message.broadcast.enabled":
"Enable broadcast action (default: true).",
"tools.message.broadcast.enabled": "Enable broadcast action (default: true).",
"tools.web.search.enabled": "Enable the web_search tool (requires Brave API key).",
"tools.web.search.provider": 'Search provider (only "brave" supported today).',
"tools.web.search.apiKey": "Brave Search API key (fallback: BRAVE_API_KEY env var).",

View File

@@ -202,8 +202,7 @@ export const agentHandlers: GatewayRequestHandlers = {
const lastChannel = sessionEntry?.lastChannel;
const lastTo = typeof sessionEntry?.lastTo === "string" ? sessionEntry.lastTo.trim() : "";
const resolvedAccountId =
normalizeAccountId(request.accountId) ??
normalizeAccountId(sessionEntry?.lastAccountId);
normalizeAccountId(request.accountId) ?? normalizeAccountId(sessionEntry?.lastAccountId);
const wantsDelivery = request.deliver === true;

View File

@@ -157,7 +157,10 @@ describe("runMessageAction context isolation", () => {
to: "imessage:+15551230000",
message: "hi",
},
toolContext: { currentChannelId: "imessage:+15551234567", currentChannelProvider: "imessage" },
toolContext: {
currentChannelId: "imessage:+15551234567",
currentChannelProvider: "imessage",
},
dryRun: true,
});

View File

@@ -13,7 +13,10 @@ import type {
} from "../../channels/plugins/types.js";
import type { ClawdbotConfig } from "../../config/config.js";
import type { GatewayClientMode, GatewayClientName } from "../../utils/message-channel.js";
import { listConfiguredMessageChannels, resolveMessageChannelSelection } from "./channel-selection.js";
import {
listConfiguredMessageChannels,
resolveMessageChannelSelection,
} from "./channel-selection.js";
import type { OutboundSendDeps } from "./deliver.js";
import type { MessagePollResult, MessageSendResult } from "./message.js";
import { sendMessage, sendPoll } from "./message.js";

View File

@@ -77,7 +77,8 @@ export function enforceCrossContextPolicy(params: {
if (params.cfg.tools?.message?.allowCrossContextSend) return;
const currentProvider = params.toolContext?.currentChannelProvider;
const allowWithinProvider = params.cfg.tools?.message?.crossContext?.allowWithinProvider !== false;
const allowWithinProvider =
params.cfg.tools?.message?.crossContext?.allowWithinProvider !== false;
const allowAcrossProviders =
params.cfg.tools?.message?.crossContext?.allowAcrossProviders === true;
@@ -132,7 +133,7 @@ export async function buildCrossContextDecoration(params: {
const adapter = getChannelMessageAdapter(params.channel);
const embeds = adapter.supportsEmbeds
? adapter.buildCrossContextEmbeds?.(originLabel) ?? undefined
? (adapter.buildCrossContextEmbeds?.(originLabel) ?? undefined)
: undefined;
return { prefix, suffix, embeds };

View File

@@ -31,7 +31,10 @@ function normalizeQuery(value: string): string {
}
function stripTargetPrefixes(value: string): string {
return value.replace(/^(channel|group|user):/i, "").replace(/^[@#]/, "").trim();
return value
.replace(/^(channel|group|user):/i, "")
.replace(/^[@#]/, "")
.trim();
}
function preserveTargetCase(channel: ChannelId, raw: string, normalized: string): string {
@@ -132,7 +135,7 @@ async function listDirectoryEntries(params: {
const runtime = params.runtime ?? defaultRuntime;
const useLive = params.source === "live";
if (params.kind === "user") {
const fn = useLive ? directory.listPeersLive ?? directory.listPeers : directory.listPeers;
const fn = useLive ? (directory.listPeersLive ?? directory.listPeers) : directory.listPeers;
if (!fn) return [];
return await fn({
cfg: params.cfg,
@@ -142,7 +145,7 @@ async function listDirectoryEntries(params: {
runtime,
});
}
const fn = useLive ? directory.listGroupsLive ?? directory.listGroups : directory.listGroups;
const fn = useLive ? (directory.listGroupsLive ?? directory.listGroups) : directory.listGroups;
if (!fn) return [];
return await fn({
cfg: params.cfg,
@@ -254,9 +257,7 @@ export async function resolveMessagingTarget(params: {
if (match.kind === "ambiguous") {
return {
ok: false,
error: new Error(
`Ambiguous target "${raw}". Provide a unique name or an explicit id.`,
),
error: new Error(`Ambiguous target "${raw}". Provide a unique name or an explicit id.`),
candidates: match.entries,
};
}

View File

@@ -323,15 +323,13 @@ export const buildTelegramMessageContext = async ({
replyTarget.id ? ` id:${replyTarget.id}` : ""
}]\n${replyTarget.body}\n[/Replying]`
: "";
const groupLabel = isGroup ? buildGroupLabel(msg, chatId, resolvedThreadId) : undefined;
const body = formatAgentEnvelope({
channel: "Telegram",
from: isGroup
? (groupLabel ?? `group:${chatId}`)
: buildSenderLabel(msg, senderId || chatId),
timestamp: msg.date ? msg.date * 1000 : undefined,
body: `${bodyText}${replySuffix}`,
});
const groupLabel = isGroup ? buildGroupLabel(msg, chatId, resolvedThreadId) : undefined;
const body = formatAgentEnvelope({
channel: "Telegram",
from: isGroup ? (groupLabel ?? `group:${chatId}`) : buildSenderLabel(msg, senderId || chatId),
timestamp: msg.date ? msg.date * 1000 : undefined,
body: `${bodyText}${replySuffix}`,
});
let combinedBody = body;
if (isGroup && historyKey && historyLimit > 0) {
combinedBody = buildPendingHistoryContextFromMap({

View File

@@ -171,17 +171,17 @@ describe("createTelegramBot", () => {
getFile: async () => ({ download: async () => new Uint8Array() }),
});
expect(replySpy).toHaveBeenCalledTimes(1);
const payload = replySpy.mock.calls[0][0];
expect(payload.WasMentioned).toBe(true);
expect(payload.SenderName).toBe("Ada");
expect(payload.SenderId).toBe("9");
expect(payload.Body).toMatch(/^\[Telegram Test Group id:7 2025-01-09T00:00Z\]/);
});
it("keeps group envelope headers stable (sender identity is separate)", async () => {
onSpy.mockReset();
const replySpy = replyModule.__replySpy as unknown as ReturnType<typeof vi.fn>;
replySpy.mockReset();
expect(replySpy).toHaveBeenCalledTimes(1);
const payload = replySpy.mock.calls[0][0];
expect(payload.WasMentioned).toBe(true);
expect(payload.SenderName).toBe("Ada");
expect(payload.SenderId).toBe("9");
expect(payload.Body).toMatch(/^\[Telegram Test Group id:7 2025-01-09T00:00Z\]/);
});
it("keeps group envelope headers stable (sender identity is separate)", async () => {
onSpy.mockReset();
const replySpy = replyModule.__replySpy as unknown as ReturnType<typeof vi.fn>;
replySpy.mockReset();
loadConfig.mockReturnValue({
channels: {
@@ -212,13 +212,13 @@ describe("createTelegramBot", () => {
getFile: async () => ({ download: async () => new Uint8Array() }),
});
expect(replySpy).toHaveBeenCalledTimes(1);
const payload = replySpy.mock.calls[0][0];
expect(payload.SenderName).toBe("Ada Lovelace");
expect(payload.SenderId).toBe("99");
expect(payload.SenderUsername).toBe("ada");
expect(payload.Body).toMatch(/^\[Telegram Ops id:42 2025-01-09T00:00Z\]/);
});
expect(replySpy).toHaveBeenCalledTimes(1);
const payload = replySpy.mock.calls[0][0];
expect(payload.SenderName).toBe("Ada Lovelace");
expect(payload.SenderId).toBe("99");
expect(payload.SenderUsername).toBe("ada");
expect(payload.Body).toMatch(/^\[Telegram Ops id:42 2025-01-09T00:00Z\]/);
});
it("reacts to mention-gated group messages when ackReaction is enabled", async () => {
onSpy.mockReset();
setMessageReactionSpy.mockReset();

View File

@@ -8,8 +8,7 @@ export type DeliveryContext = {
export function normalizeDeliveryContext(context?: DeliveryContext): DeliveryContext | undefined {
if (!context) return undefined;
const channel =
typeof context.channel === "string" ? context.channel.trim() : undefined;
const channel = typeof context.channel === "string" ? context.channel.trim() : undefined;
const to = typeof context.to === "string" ? context.to.trim() : undefined;
const accountId = normalizeAccountId(context.accountId);
if (!channel && !to && !accountId) return undefined;

View File

@@ -216,7 +216,12 @@ describe("broadcast groups", () => {
expect(resolver).toHaveBeenCalledTimes(2);
for (const call of resolver.mock.calls.slice(0, 2)) {
const payload = call[0] as { Body: string; SenderName?: string; SenderE164?: string; SenderId?: string };
const payload = call[0] as {
Body: string;
SenderName?: string;
SenderE164?: string;
SenderId?: string;
};
expect(payload.Body).toContain("Chat messages since your last reply");
expect(payload.Body).toContain("Alice (+111): hello group");
expect(payload.Body).toContain("[message_id: g1]");

View File

@@ -9,7 +9,10 @@ import {
} from "../../../auto-reply/reply/response-prefix-template.js";
import { resolveTextChunkLimit } from "../../../auto-reply/chunk.js";
import { formatAgentEnvelope } from "../../../auto-reply/envelope.js";
import { buildHistoryContextFromEntries, type HistoryEntry } from "../../../auto-reply/reply/history.js";
import {
buildHistoryContextFromEntries,
type HistoryEntry,
} from "../../../auto-reply/reply/history.js";
import { dispatchReplyWithBufferedBlockDispatcher } from "../../../auto-reply/reply/provider-dispatcher.js";
import type { getReplyFromConfig } from "../../../auto-reply/reply.js";
import type { ReplyPayload } from "../../../auto-reply/types.js";
@@ -88,7 +91,9 @@ export async function processMessage(params: {
currentMessage: combinedBody,
excludeLast: false,
formatEntry: (entry) => {
const bodyWithId = entry.messageId ? `${entry.body}\n[message_id: ${entry.messageId}]` : entry.body;
const bodyWithId = entry.messageId
? `${entry.body}\n[message_id: ${entry.messageId}]`
: entry.body;
return formatAgentEnvelope({
channel: "WhatsApp",
from: conversationId,