chore: migrate to oxlint and oxfmt
Co-authored-by: Christoph Nakazawa <christoph.pojer@gmail.com>
This commit is contained in:
@@ -1,9 +1,6 @@
|
||||
import type { ClawdbotConfig } from "../config/config.js";
|
||||
import type { SignalAccountConfig } from "../config/types.js";
|
||||
import {
|
||||
DEFAULT_ACCOUNT_ID,
|
||||
normalizeAccountId,
|
||||
} from "../routing/session-key.js";
|
||||
import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "../routing/session-key.js";
|
||||
|
||||
export type ResolvedSignalAccount = {
|
||||
accountId: string;
|
||||
@@ -41,12 +38,10 @@ function resolveAccountConfig(
|
||||
return accounts[accountId] as SignalAccountConfig | undefined;
|
||||
}
|
||||
|
||||
function mergeSignalAccountConfig(
|
||||
cfg: ClawdbotConfig,
|
||||
accountId: string,
|
||||
): SignalAccountConfig {
|
||||
const { accounts: _ignored, ...base } = (cfg.channels?.signal ??
|
||||
{}) as SignalAccountConfig & { accounts?: unknown };
|
||||
function mergeSignalAccountConfig(cfg: ClawdbotConfig, accountId: string): SignalAccountConfig {
|
||||
const { accounts: _ignored, ...base } = (cfg.channels?.signal ?? {}) as SignalAccountConfig & {
|
||||
accounts?: unknown;
|
||||
};
|
||||
const account = resolveAccountConfig(cfg, accountId) ?? {};
|
||||
return { ...base, ...account };
|
||||
}
|
||||
@@ -65,11 +60,11 @@ export function resolveSignalAccount(params: {
|
||||
const baseUrl = merged.httpUrl?.trim() || `http://${host}:${port}`;
|
||||
const configured = Boolean(
|
||||
merged.account?.trim() ||
|
||||
merged.httpUrl?.trim() ||
|
||||
merged.cliPath?.trim() ||
|
||||
merged.httpHost?.trim() ||
|
||||
typeof merged.httpPort === "number" ||
|
||||
typeof merged.autoStart === "boolean",
|
||||
merged.httpUrl?.trim() ||
|
||||
merged.cliPath?.trim() ||
|
||||
merged.httpHost?.trim() ||
|
||||
typeof merged.httpPort === "number" ||
|
||||
typeof merged.autoStart === "boolean",
|
||||
);
|
||||
return {
|
||||
accountId,
|
||||
@@ -81,9 +76,7 @@ export function resolveSignalAccount(params: {
|
||||
};
|
||||
}
|
||||
|
||||
export function listEnabledSignalAccounts(
|
||||
cfg: ClawdbotConfig,
|
||||
): ResolvedSignalAccount[] {
|
||||
export function listEnabledSignalAccounts(cfg: ClawdbotConfig): ResolvedSignalAccount[] {
|
||||
return listSignalAccountIds(cfg)
|
||||
.map((accountId) => resolveSignalAccount({ cfg, accountId }))
|
||||
.filter((account) => account.enabled);
|
||||
|
||||
@@ -35,11 +35,7 @@ function normalizeBaseUrl(url: string): string {
|
||||
return `http://${trimmed}`.replace(/\/+$/, "");
|
||||
}
|
||||
|
||||
async function fetchWithTimeout(
|
||||
url: string,
|
||||
init: RequestInit,
|
||||
timeoutMs: number,
|
||||
) {
|
||||
async function fetchWithTimeout(url: string, init: RequestInit, timeoutMs: number) {
|
||||
const controller = new AbortController();
|
||||
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
||||
try {
|
||||
@@ -93,11 +89,7 @@ export async function signalCheck(
|
||||
): Promise<{ ok: boolean; status?: number | null; error?: string | null }> {
|
||||
const normalized = normalizeBaseUrl(baseUrl);
|
||||
try {
|
||||
const res = await fetchWithTimeout(
|
||||
`${normalized}/api/v1/check`,
|
||||
{ method: "GET" },
|
||||
timeoutMs,
|
||||
);
|
||||
const res = await fetchWithTimeout(`${normalized}/api/v1/check`, { method: "GET" }, timeoutMs);
|
||||
if (!res.ok) {
|
||||
return { ok: false, status: res.status, error: `HTTP ${res.status}` };
|
||||
}
|
||||
@@ -127,9 +119,7 @@ export async function streamSignalEvents(params: {
|
||||
signal: params.abortSignal,
|
||||
});
|
||||
if (!res.ok || !res.body) {
|
||||
throw new Error(
|
||||
`Signal SSE failed (${res.status} ${res.statusText || "error"})`,
|
||||
);
|
||||
throw new Error(`Signal SSE failed (${res.status} ${res.statusText || "error"})`);
|
||||
}
|
||||
|
||||
const reader = res.body.getReader();
|
||||
@@ -173,9 +163,7 @@ export async function streamSignalEvents(params: {
|
||||
if (field === "event") {
|
||||
currentEvent.event = value;
|
||||
} else if (field === "data") {
|
||||
currentEvent.data = currentEvent.data
|
||||
? `${currentEvent.data}\n${value}`
|
||||
: value;
|
||||
currentEvent.data = currentEvent.data ? `${currentEvent.data}\n${value}` : value;
|
||||
} else if (field === "id") {
|
||||
currentEvent.id = value;
|
||||
}
|
||||
|
||||
@@ -4,9 +4,7 @@ import { classifySignalCliLogLine } from "./daemon.js";
|
||||
|
||||
describe("classifySignalCliLogLine", () => {
|
||||
it("treats INFO/DEBUG as log (even if emitted on stderr)", () => {
|
||||
expect(classifySignalCliLogLine("INFO DaemonCommand - Started")).toBe(
|
||||
"log",
|
||||
);
|
||||
expect(classifySignalCliLogLine("INFO DaemonCommand - Started")).toBe("log");
|
||||
expect(classifySignalCliLogLine("DEBUG Something")).toBe("log");
|
||||
});
|
||||
|
||||
@@ -17,12 +15,8 @@ describe("classifySignalCliLogLine", () => {
|
||||
});
|
||||
|
||||
it("treats failures without explicit severity as error", () => {
|
||||
expect(
|
||||
classifySignalCliLogLine("Failed to initialize HTTP Server - oops"),
|
||||
).toBe("error");
|
||||
expect(classifySignalCliLogLine('Exception in thread "main"')).toBe(
|
||||
"error",
|
||||
);
|
||||
expect(classifySignalCliLogLine("Failed to initialize HTTP Server - oops")).toBe("error");
|
||||
expect(classifySignalCliLogLine('Exception in thread "main"')).toBe("error");
|
||||
});
|
||||
|
||||
it("returns null for empty lines", () => {
|
||||
|
||||
@@ -9,8 +9,7 @@ type SignalAllowEntry =
|
||||
| { kind: "phone"; e164: string }
|
||||
| { kind: "uuid"; raw: string };
|
||||
|
||||
const UUID_HYPHENATED_RE =
|
||||
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
||||
const UUID_HYPHENATED_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
||||
const UUID_COMPACT_RE = /^[0-9a-f]{32}$/i;
|
||||
|
||||
function looksLikeUuid(value: string): boolean {
|
||||
@@ -88,10 +87,7 @@ function parseSignalAllowEntry(entry: string): SignalAllowEntry | null {
|
||||
return { kind: "phone", e164: normalizeE164(stripped) };
|
||||
}
|
||||
|
||||
export function isSignalSenderAllowed(
|
||||
sender: SignalSender,
|
||||
allowFrom: string[],
|
||||
): boolean {
|
||||
export function isSignalSenderAllowed(sender: SignalSender, allowFrom: string[]): boolean {
|
||||
if (allowFrom.length === 0) return false;
|
||||
const parsed = allowFrom
|
||||
.map(parseSignalAllowEntry)
|
||||
|
||||
@@ -28,10 +28,8 @@ vi.mock("./send.js", () => ({
|
||||
}));
|
||||
|
||||
vi.mock("../pairing/pairing-store.js", () => ({
|
||||
readChannelAllowFromStore: (...args: unknown[]) =>
|
||||
readAllowFromStoreMock(...args),
|
||||
upsertChannelPairingRequest: (...args: unknown[]) =>
|
||||
upsertPairingRequestMock(...args),
|
||||
readChannelAllowFromStore: (...args: unknown[]) => readAllowFromStoreMock(...args),
|
||||
upsertChannelPairingRequest: (...args: unknown[]) => upsertPairingRequestMock(...args),
|
||||
}));
|
||||
|
||||
vi.mock("../config/sessions.js", () => ({
|
||||
@@ -70,9 +68,7 @@ beforeEach(() => {
|
||||
signalCheckMock.mockReset().mockResolvedValue({});
|
||||
signalRpcRequestMock.mockReset().mockResolvedValue({});
|
||||
readAllowFromStoreMock.mockReset().mockResolvedValue([]);
|
||||
upsertPairingRequestMock
|
||||
.mockReset()
|
||||
.mockResolvedValue({ code: "PAIRCODE", created: true });
|
||||
upsertPairingRequestMock.mockReset().mockResolvedValue({ code: "PAIRCODE", created: true });
|
||||
resetSystemEventsForTest();
|
||||
});
|
||||
|
||||
|
||||
@@ -2,10 +2,7 @@ import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { resetInboundDedupe } from "../auto-reply/reply/inbound-dedupe.js";
|
||||
import type { ClawdbotConfig } from "../config/config.js";
|
||||
import {
|
||||
peekSystemEvents,
|
||||
resetSystemEventsForTest,
|
||||
} from "../infra/system-events.js";
|
||||
import { peekSystemEvents, resetSystemEventsForTest } from "../infra/system-events.js";
|
||||
import { resolveAgentRoute } from "../routing/resolve-route.js";
|
||||
import { normalizeE164 } from "../utils.js";
|
||||
import { monitorSignalProvider } from "./monitor.js";
|
||||
@@ -34,10 +31,8 @@ vi.mock("./send.js", () => ({
|
||||
}));
|
||||
|
||||
vi.mock("../pairing/pairing-store.js", () => ({
|
||||
readChannelAllowFromStore: (...args: unknown[]) =>
|
||||
readAllowFromStoreMock(...args),
|
||||
upsertChannelPairingRequest: (...args: unknown[]) =>
|
||||
upsertPairingRequestMock(...args),
|
||||
readChannelAllowFromStore: (...args: unknown[]) => readAllowFromStoreMock(...args),
|
||||
upsertChannelPairingRequest: (...args: unknown[]) => upsertPairingRequestMock(...args),
|
||||
}));
|
||||
|
||||
vi.mock("../config/sessions.js", () => ({
|
||||
@@ -76,9 +71,7 @@ beforeEach(() => {
|
||||
signalCheckMock.mockReset().mockResolvedValue({});
|
||||
signalRpcRequestMock.mockReset().mockResolvedValue({});
|
||||
readAllowFromStoreMock.mockReset().mockResolvedValue([]);
|
||||
upsertPairingRequestMock
|
||||
.mockReset()
|
||||
.mockResolvedValue({ code: "PAIRCODE", created: true });
|
||||
upsertPairingRequestMock.mockReset().mockResolvedValue({ code: "PAIRCODE", created: true });
|
||||
resetSystemEventsForTest();
|
||||
});
|
||||
|
||||
@@ -165,12 +158,8 @@ describe("monitorSignalProvider tool results", () => {
|
||||
expect(replyMock).not.toHaveBeenCalled();
|
||||
expect(upsertPairingRequestMock).toHaveBeenCalled();
|
||||
expect(sendMock).toHaveBeenCalledTimes(1);
|
||||
expect(String(sendMock.mock.calls[0]?.[1] ?? "")).toContain(
|
||||
"Your Signal number: +15550001111",
|
||||
);
|
||||
expect(String(sendMock.mock.calls[0]?.[1] ?? "")).toContain(
|
||||
"Pairing code: PAIRCODE",
|
||||
);
|
||||
expect(String(sendMock.mock.calls[0]?.[1] ?? "")).toContain("Your Signal number: +15550001111");
|
||||
expect(String(sendMock.mock.calls[0]?.[1] ?? "")).toContain("Pairing code: PAIRCODE");
|
||||
});
|
||||
|
||||
it("ignores reaction-only messages", async () => {
|
||||
@@ -299,9 +288,7 @@ describe("monitorSignalProvider tool results", () => {
|
||||
peer: { kind: "dm", id: normalizeE164("+15550001111") },
|
||||
});
|
||||
const events = peekSystemEvents(route.sessionKey);
|
||||
expect(events.some((text) => text.includes("Signal reaction added"))).toBe(
|
||||
true,
|
||||
);
|
||||
expect(events.some((text) => text.includes("Signal reaction added"))).toBe(true);
|
||||
});
|
||||
|
||||
it("notifies on own reactions when target includes uuid + phone", async () => {
|
||||
@@ -357,9 +344,7 @@ describe("monitorSignalProvider tool results", () => {
|
||||
peer: { kind: "dm", id: normalizeE164("+15550001111") },
|
||||
});
|
||||
const events = peekSystemEvents(route.sessionKey);
|
||||
expect(events.some((text) => text.includes("Signal reaction added"))).toBe(
|
||||
true,
|
||||
);
|
||||
expect(events.some((text) => text.includes("Signal reaction added"))).toBe(true);
|
||||
});
|
||||
|
||||
it("processes messages when reaction metadata is present", async () => {
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
import { chunkText, resolveTextChunkLimit } from "../auto-reply/chunk.js";
|
||||
import {
|
||||
DEFAULT_GROUP_HISTORY_LIMIT,
|
||||
type HistoryEntry,
|
||||
} from "../auto-reply/reply/history.js";
|
||||
import { DEFAULT_GROUP_HISTORY_LIMIT, type HistoryEntry } from "../auto-reply/reply/history.js";
|
||||
import type { ReplyPayload } from "../auto-reply/types.js";
|
||||
import type { ClawdbotConfig } from "../config/config.js";
|
||||
import { loadConfig } from "../config/config.js";
|
||||
@@ -80,9 +77,7 @@ type SignalReactionTarget = {
|
||||
display: string;
|
||||
};
|
||||
|
||||
function resolveSignalReactionTargets(
|
||||
reaction: SignalReactionMessage,
|
||||
): SignalReactionTarget[] {
|
||||
function resolveSignalReactionTargets(reaction: SignalReactionMessage): SignalReactionTarget[] {
|
||||
const targets: SignalReactionTarget[] = [];
|
||||
const uuid = reaction.targetAuthorUuid?.trim();
|
||||
if (uuid) {
|
||||
@@ -102,12 +97,8 @@ function isSignalReactionMessage(
|
||||
if (!reaction) return false;
|
||||
const emoji = reaction.emoji?.trim();
|
||||
const timestamp = reaction.targetSentTimestamp;
|
||||
const hasTarget = Boolean(
|
||||
reaction.targetAuthor?.trim() || reaction.targetAuthorUuid?.trim(),
|
||||
);
|
||||
return Boolean(
|
||||
emoji && typeof timestamp === "number" && timestamp > 0 && hasTarget,
|
||||
);
|
||||
const hasTarget = Boolean(reaction.targetAuthor?.trim() || reaction.targetAuthorUuid?.trim());
|
||||
return Boolean(emoji && typeof timestamp === "number" && timestamp > 0 && hasTarget);
|
||||
}
|
||||
|
||||
function shouldEmitSignalReactionNotification(params: {
|
||||
@@ -146,12 +137,8 @@ function buildSignalReactionSystemEventText(params: {
|
||||
groupLabel?: string;
|
||||
}) {
|
||||
const base = `Signal reaction added: ${params.emojiLabel} by ${params.actorLabel} msg ${params.messageId}`;
|
||||
const withTarget = params.targetLabel
|
||||
? `${base} from ${params.targetLabel}`
|
||||
: base;
|
||||
return params.groupLabel
|
||||
? `${withTarget} in ${params.groupLabel}`
|
||||
: withTarget;
|
||||
const withTarget = params.targetLabel ? `${base} from ${params.targetLabel}` : base;
|
||||
return params.groupLabel ? `${withTarget} in ${params.groupLabel}` : withTarget;
|
||||
}
|
||||
|
||||
async function waitForSignalDaemonReady(params: {
|
||||
@@ -167,15 +154,12 @@ async function waitForSignalDaemonReady(params: {
|
||||
if (params.abortSignal?.aborted) return;
|
||||
const res = await signalCheck(params.baseUrl, 1000);
|
||||
if (res.ok) return;
|
||||
lastError =
|
||||
res.error ?? (res.status ? `HTTP ${res.status}` : "unreachable");
|
||||
lastError = res.error ?? (res.status ? `HTTP ${res.status}` : "unreachable");
|
||||
await new Promise((r) => setTimeout(r, 150));
|
||||
}
|
||||
|
||||
params.runtime.error?.(
|
||||
danger(
|
||||
`daemon not ready after ${params.timeoutMs}ms (${lastError ?? "unknown error"})`,
|
||||
),
|
||||
danger(`daemon not ready after ${params.timeoutMs}ms (${lastError ?? "unknown error"})`),
|
||||
);
|
||||
throw new Error(`signal daemon not ready (${lastError ?? "unknown error"})`);
|
||||
}
|
||||
@@ -203,11 +187,9 @@ async function fetchAttachment(params: {
|
||||
else if (params.sender) rpcParams.recipient = params.sender;
|
||||
else return null;
|
||||
|
||||
const result = await signalRpcRequest<{ data?: string }>(
|
||||
"getAttachment",
|
||||
rpcParams,
|
||||
{ baseUrl: params.baseUrl },
|
||||
);
|
||||
const result = await signalRpcRequest<{ data?: string }>("getAttachment", rpcParams, {
|
||||
baseUrl: params.baseUrl,
|
||||
});
|
||||
if (!result?.data) return null;
|
||||
const buffer = Buffer.from(result.data, "base64");
|
||||
const saved = await saveMediaBuffer(
|
||||
@@ -229,19 +211,9 @@ async function deliverReplies(params: {
|
||||
maxBytes: number;
|
||||
textLimit: number;
|
||||
}) {
|
||||
const {
|
||||
replies,
|
||||
target,
|
||||
baseUrl,
|
||||
account,
|
||||
accountId,
|
||||
runtime,
|
||||
maxBytes,
|
||||
textLimit,
|
||||
} = params;
|
||||
const { replies, target, baseUrl, account, accountId, runtime, maxBytes, textLimit } = params;
|
||||
for (const payload of replies) {
|
||||
const mediaList =
|
||||
payload.mediaUrls ?? (payload.mediaUrl ? [payload.mediaUrl] : []);
|
||||
const mediaList = payload.mediaUrls ?? (payload.mediaUrl ? [payload.mediaUrl] : []);
|
||||
const text = payload.text ?? "";
|
||||
if (!text && mediaList.length === 0) continue;
|
||||
if (mediaList.length === 0) {
|
||||
@@ -271,9 +243,7 @@ async function deliverReplies(params: {
|
||||
}
|
||||
}
|
||||
|
||||
export async function monitorSignalProvider(
|
||||
opts: MonitorSignalOpts = {},
|
||||
): Promise<void> {
|
||||
export async function monitorSignalProvider(opts: MonitorSignalOpts = {}): Promise<void> {
|
||||
const runtime = resolveRuntime(opts);
|
||||
const cfg = opts.config ?? loadConfig();
|
||||
const accountInfo = resolveSignalAccount({
|
||||
@@ -291,9 +261,7 @@ export async function monitorSignalProvider(
|
||||
const baseUrl = opts.baseUrl?.trim() || accountInfo.baseUrl;
|
||||
const account = opts.account?.trim() || accountInfo.config.account?.trim();
|
||||
const dmPolicy = accountInfo.config.dmPolicy ?? "pairing";
|
||||
const allowFrom = normalizeAllowList(
|
||||
opts.allowFrom ?? accountInfo.config.allowFrom,
|
||||
);
|
||||
const allowFrom = normalizeAllowList(opts.allowFrom ?? accountInfo.config.allowFrom);
|
||||
const groupAllowFrom = normalizeAllowList(
|
||||
opts.groupAllowFrom ??
|
||||
accountInfo.config.groupAllowFrom ??
|
||||
@@ -303,24 +271,16 @@ export async function monitorSignalProvider(
|
||||
);
|
||||
const groupPolicy = accountInfo.config.groupPolicy ?? "allowlist";
|
||||
const reactionMode = accountInfo.config.reactionNotifications ?? "own";
|
||||
const reactionAllowlist = normalizeAllowList(
|
||||
accountInfo.config.reactionAllowlist,
|
||||
);
|
||||
const mediaMaxBytes =
|
||||
(opts.mediaMaxMb ?? accountInfo.config.mediaMaxMb ?? 8) * 1024 * 1024;
|
||||
const ignoreAttachments =
|
||||
opts.ignoreAttachments ?? accountInfo.config.ignoreAttachments ?? false;
|
||||
const reactionAllowlist = normalizeAllowList(accountInfo.config.reactionAllowlist);
|
||||
const mediaMaxBytes = (opts.mediaMaxMb ?? accountInfo.config.mediaMaxMb ?? 8) * 1024 * 1024;
|
||||
const ignoreAttachments = opts.ignoreAttachments ?? accountInfo.config.ignoreAttachments ?? false;
|
||||
|
||||
const autoStart =
|
||||
opts.autoStart ??
|
||||
accountInfo.config.autoStart ??
|
||||
!accountInfo.config.httpUrl;
|
||||
const autoStart = opts.autoStart ?? accountInfo.config.autoStart ?? !accountInfo.config.httpUrl;
|
||||
let daemonHandle: ReturnType<typeof spawnSignalDaemon> | null = null;
|
||||
|
||||
if (autoStart) {
|
||||
const cliPath = opts.cliPath ?? accountInfo.config.cliPath ?? "signal-cli";
|
||||
const httpHost =
|
||||
opts.httpHost ?? accountInfo.config.httpHost ?? "127.0.0.1";
|
||||
const httpHost = opts.httpHost ?? accountInfo.config.httpHost ?? "127.0.0.1";
|
||||
const httpPort = opts.httpPort ?? accountInfo.config.httpPort ?? 8080;
|
||||
daemonHandle = spawnSignalDaemon({
|
||||
cliPath,
|
||||
@@ -328,11 +288,9 @@ export async function monitorSignalProvider(
|
||||
httpHost,
|
||||
httpPort,
|
||||
receiveMode: opts.receiveMode ?? accountInfo.config.receiveMode,
|
||||
ignoreAttachments:
|
||||
opts.ignoreAttachments ?? accountInfo.config.ignoreAttachments,
|
||||
ignoreAttachments: opts.ignoreAttachments ?? accountInfo.config.ignoreAttachments,
|
||||
ignoreStories: opts.ignoreStories ?? accountInfo.config.ignoreStories,
|
||||
sendReadReceipts:
|
||||
opts.sendReadReceipts ?? accountInfo.config.sendReadReceipts,
|
||||
sendReadReceipts: opts.sendReadReceipts ?? accountInfo.config.sendReadReceipts,
|
||||
runtime,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,13 +1,7 @@
|
||||
import {
|
||||
resolveEffectiveMessagesConfig,
|
||||
resolveHumanDelayConfig,
|
||||
} from "../../agents/identity.js";
|
||||
import { resolveEffectiveMessagesConfig, resolveHumanDelayConfig } from "../../agents/identity.js";
|
||||
import { formatAgentEnvelope } from "../../auto-reply/envelope.js";
|
||||
import { dispatchReplyFromConfig } from "../../auto-reply/reply/dispatch-from-config.js";
|
||||
import {
|
||||
buildHistoryContextFromMap,
|
||||
clearHistoryEntries,
|
||||
} from "../../auto-reply/reply/history.js";
|
||||
import { buildHistoryContextFromMap, clearHistoryEntries } from "../../auto-reply/reply/history.js";
|
||||
import { createReplyDispatcher } from "../../auto-reply/reply/reply-dispatcher.js";
|
||||
import { resolveStorePath, updateLastRoute } from "../../config/sessions.js";
|
||||
import { danger, logVerbose, shouldLogVerbose } from "../../globals.js";
|
||||
@@ -31,10 +25,7 @@ import {
|
||||
} from "../identity.js";
|
||||
import { sendMessageSignal } from "../send.js";
|
||||
|
||||
import type {
|
||||
SignalEventHandlerDeps,
|
||||
SignalReceivePayload,
|
||||
} from "./event-handler.types.js";
|
||||
import type { SignalEventHandlerDeps, SignalReceivePayload } from "./event-handler.types.js";
|
||||
|
||||
export function createSignalEventHandler(deps: SignalEventHandlerDeps) {
|
||||
return async (event: { event?: string; data?: string }) => {
|
||||
@@ -60,8 +51,7 @@ export function createSignalEventHandler(deps: SignalEventHandlerDeps) {
|
||||
if (sender.e164 === normalizeE164(deps.account)) return;
|
||||
}
|
||||
|
||||
const dataMessage =
|
||||
envelope.dataMessage ?? envelope.editMessage?.dataMessage;
|
||||
const dataMessage = envelope.dataMessage ?? envelope.editMessage?.dataMessage;
|
||||
const reaction = deps.isSignalReactionMessage(envelope.reactionMessage)
|
||||
? envelope.reactionMessage
|
||||
: deps.isSignalReactionMessage(dataMessage?.reaction)
|
||||
@@ -70,8 +60,7 @@ export function createSignalEventHandler(deps: SignalEventHandlerDeps) {
|
||||
const messageText = (dataMessage?.message ?? "").trim();
|
||||
const quoteText = dataMessage?.quote?.text?.trim() ?? "";
|
||||
const hasBodyContent =
|
||||
Boolean(messageText || quoteText) ||
|
||||
Boolean(!reaction && dataMessage?.attachments?.length);
|
||||
Boolean(messageText || quoteText) || Boolean(!reaction && dataMessage?.attachments?.length);
|
||||
|
||||
if (reaction && !hasBodyContent) {
|
||||
if (reaction.isRemove) return; // Ignore reaction removals
|
||||
@@ -102,9 +91,7 @@ export function createSignalEventHandler(deps: SignalEventHandlerDeps) {
|
||||
id: isGroup ? (groupId ?? "unknown") : senderPeerId,
|
||||
},
|
||||
});
|
||||
const groupLabel = isGroup
|
||||
? `${groupName ?? "Signal Group"} id:${groupId}`
|
||||
: undefined;
|
||||
const groupLabel = isGroup ? `${groupName ?? "Signal Group"} id:${groupId}` : undefined;
|
||||
const messageId = reaction.targetSentTimestamp
|
||||
? String(reaction.targetSentTimestamp)
|
||||
: "unknown";
|
||||
@@ -141,15 +128,11 @@ export function createSignalEventHandler(deps: SignalEventHandlerDeps) {
|
||||
const groupId = dataMessage.groupInfo?.groupId ?? undefined;
|
||||
const groupName = dataMessage.groupInfo?.groupName ?? undefined;
|
||||
const isGroup = Boolean(groupId);
|
||||
const storeAllowFrom = await readChannelAllowFromStore("signal").catch(
|
||||
() => [],
|
||||
);
|
||||
const storeAllowFrom = await readChannelAllowFromStore("signal").catch(() => []);
|
||||
const effectiveDmAllow = [...deps.allowFrom, ...storeAllowFrom];
|
||||
const effectiveGroupAllow = [...deps.groupAllowFrom, ...storeAllowFrom];
|
||||
const dmAllowed =
|
||||
deps.dmPolicy === "open"
|
||||
? true
|
||||
: isSignalSenderAllowed(sender, effectiveDmAllow);
|
||||
deps.dmPolicy === "open" ? true : isSignalSenderAllowed(sender, effectiveDmAllow);
|
||||
|
||||
if (!isGroup) {
|
||||
if (deps.dmPolicy === "disabled") return;
|
||||
@@ -179,15 +162,11 @@ export function createSignalEventHandler(deps: SignalEventHandlerDeps) {
|
||||
},
|
||||
);
|
||||
} catch (err) {
|
||||
logVerbose(
|
||||
`signal pairing reply failed for ${senderId}: ${String(err)}`,
|
||||
);
|
||||
logVerbose(`signal pairing reply failed for ${senderId}: ${String(err)}`);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logVerbose(
|
||||
`Blocked signal sender ${senderDisplay} (dmPolicy=${deps.dmPolicy})`,
|
||||
);
|
||||
logVerbose(`Blocked signal sender ${senderDisplay} (dmPolicy=${deps.dmPolicy})`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -198,15 +177,11 @@ export function createSignalEventHandler(deps: SignalEventHandlerDeps) {
|
||||
}
|
||||
if (isGroup && deps.groupPolicy === "allowlist") {
|
||||
if (effectiveGroupAllow.length === 0) {
|
||||
logVerbose(
|
||||
"Blocked signal group message (groupPolicy: allowlist, no groupAllowFrom)",
|
||||
);
|
||||
logVerbose("Blocked signal group message (groupPolicy: allowlist, no groupAllowFrom)");
|
||||
return;
|
||||
}
|
||||
if (!isSignalSenderAllowed(sender, effectiveGroupAllow)) {
|
||||
logVerbose(
|
||||
`Blocked signal group sender ${senderDisplay} (not in groupAllowFrom)`,
|
||||
);
|
||||
logVerbose(`Blocked signal group sender ${senderDisplay} (not in groupAllowFrom)`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -233,8 +208,7 @@ export function createSignalEventHandler(deps: SignalEventHandlerDeps) {
|
||||
});
|
||||
if (fetched) {
|
||||
mediaPath = fetched.path;
|
||||
mediaType =
|
||||
fetched.contentType ?? firstAttachment.contentType ?? undefined;
|
||||
mediaType = fetched.contentType ?? firstAttachment.contentType ?? undefined;
|
||||
}
|
||||
} catch (err) {
|
||||
deps.runtime.error?.(danger(`attachment fetch failed: ${String(err)}`));
|
||||
@@ -243,11 +217,9 @@ export function createSignalEventHandler(deps: SignalEventHandlerDeps) {
|
||||
|
||||
const kind = mediaKindFromMime(mediaType ?? undefined);
|
||||
if (kind) placeholder = `<media:${kind}>`;
|
||||
else if (dataMessage.attachments?.length)
|
||||
placeholder = "<media:attachment>";
|
||||
else if (dataMessage.attachments?.length) placeholder = "<media:attachment>";
|
||||
|
||||
const bodyText =
|
||||
messageText || placeholder || dataMessage.quote?.text?.trim() || "";
|
||||
const bodyText = messageText || placeholder || dataMessage.quote?.text?.trim() || "";
|
||||
if (!bodyText) return;
|
||||
|
||||
const fromLabel = isGroup
|
||||
@@ -271,9 +243,7 @@ export function createSignalEventHandler(deps: SignalEventHandlerDeps) {
|
||||
body: bodyText,
|
||||
timestamp: envelope.timestamp ?? undefined,
|
||||
messageId:
|
||||
typeof envelope.timestamp === "number"
|
||||
? String(envelope.timestamp)
|
||||
: undefined,
|
||||
typeof envelope.timestamp === "number" ? String(envelope.timestamp) : undefined,
|
||||
},
|
||||
currentMessage: combinedBody,
|
||||
formatEntry: (entry) =>
|
||||
@@ -300,9 +270,7 @@ export function createSignalEventHandler(deps: SignalEventHandlerDeps) {
|
||||
Body: combinedBody,
|
||||
RawBody: bodyText,
|
||||
CommandBody: bodyText,
|
||||
From: isGroup
|
||||
? `group:${groupId ?? "unknown"}`
|
||||
: `signal:${senderRecipient}`,
|
||||
From: isGroup ? `group:${groupId ?? "unknown"}` : `signal:${senderRecipient}`,
|
||||
To: signalTo,
|
||||
SessionKey: route.sessionKey,
|
||||
AccountId: route.accountId,
|
||||
@@ -338,15 +306,12 @@ export function createSignalEventHandler(deps: SignalEventHandlerDeps) {
|
||||
|
||||
if (shouldLogVerbose()) {
|
||||
const preview = body.slice(0, 200).replace(/\n/g, "\\n");
|
||||
logVerbose(
|
||||
`signal inbound: from=${ctxPayload.From} len=${body.length} preview="${preview}"`,
|
||||
);
|
||||
logVerbose(`signal inbound: from=${ctxPayload.From} len=${body.length} preview="${preview}"`);
|
||||
}
|
||||
|
||||
let didSendReply = false;
|
||||
const dispatcher = createReplyDispatcher({
|
||||
responsePrefix: resolveEffectiveMessagesConfig(deps.cfg, route.agentId)
|
||||
.responsePrefix,
|
||||
responsePrefix: resolveEffectiveMessagesConfig(deps.cfg, route.agentId).responsePrefix,
|
||||
humanDelay: resolveHumanDelayConfig(deps.cfg, route.agentId),
|
||||
deliver: async (payload) => {
|
||||
await deps.deliverReplies({
|
||||
@@ -362,9 +327,7 @@ export function createSignalEventHandler(deps: SignalEventHandlerDeps) {
|
||||
didSendReply = true;
|
||||
},
|
||||
onError: (err, info) => {
|
||||
deps.runtime.error?.(
|
||||
danger(`signal ${info.kind} reply failed: ${String(err)}`),
|
||||
);
|
||||
deps.runtime.error?.(danger(`signal ${info.kind} reply failed: ${String(err)}`));
|
||||
},
|
||||
});
|
||||
|
||||
@@ -374,9 +337,7 @@ export function createSignalEventHandler(deps: SignalEventHandlerDeps) {
|
||||
dispatcher,
|
||||
replyOptions: {
|
||||
disableBlockStreaming:
|
||||
typeof deps.blockStreaming === "boolean"
|
||||
? !deps.blockStreaming
|
||||
: undefined,
|
||||
typeof deps.blockStreaming === "boolean" ? !deps.blockStreaming : undefined,
|
||||
},
|
||||
});
|
||||
if (!queuedFinal) {
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
import type { HistoryEntry } from "../../auto-reply/reply/history.js";
|
||||
import type { ReplyPayload } from "../../auto-reply/types.js";
|
||||
import type { ClawdbotConfig } from "../../config/config.js";
|
||||
import type {
|
||||
DmPolicy,
|
||||
GroupPolicy,
|
||||
SignalReactionNotificationMode,
|
||||
} from "../../config/types.js";
|
||||
import type { DmPolicy, GroupPolicy, SignalReactionNotificationMode } from "../../config/types.js";
|
||||
import type { RuntimeEnv } from "../../runtime.js";
|
||||
import type { SignalSender } from "../identity.js";
|
||||
|
||||
@@ -98,9 +94,7 @@ export type SignalEventHandlerDeps = {
|
||||
maxBytes: number;
|
||||
textLimit: number;
|
||||
}) => Promise<void>;
|
||||
resolveSignalReactionTargets: (
|
||||
reaction: SignalReactionMessage,
|
||||
) => SignalReactionTarget[];
|
||||
resolveSignalReactionTargets: (reaction: SignalReactionMessage) => SignalReactionTarget[];
|
||||
isSignalReactionMessage: (
|
||||
reaction: SignalReactionMessage | null | undefined,
|
||||
) => reaction is SignalReactionMessage;
|
||||
|
||||
@@ -17,10 +17,7 @@ function parseSignalVersion(value: unknown): string | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
export async function probeSignal(
|
||||
baseUrl: string,
|
||||
timeoutMs: number,
|
||||
): Promise<SignalProbe> {
|
||||
export async function probeSignal(baseUrl: string, timeoutMs: number): Promise<SignalProbe> {
|
||||
const started = Date.now();
|
||||
const result: SignalProbe = {
|
||||
ok: false,
|
||||
|
||||
@@ -115,11 +115,10 @@ export async function sendMessageSignal(
|
||||
params.username = [target.username];
|
||||
}
|
||||
|
||||
const result = await signalRpcRequest<{ timestamp?: number }>(
|
||||
"send",
|
||||
params,
|
||||
{ baseUrl, timeoutMs: opts.timeoutMs },
|
||||
);
|
||||
const result = await signalRpcRequest<{ timestamp?: number }>("send", params, {
|
||||
baseUrl,
|
||||
timeoutMs: opts.timeoutMs,
|
||||
});
|
||||
const timestamp = result?.timestamp;
|
||||
return {
|
||||
messageId: timestamp ? String(timestamp) : "unknown",
|
||||
|
||||
@@ -53,18 +53,14 @@ export async function runSignalSseLoop({
|
||||
if (abortSignal?.aborted) return;
|
||||
reconnectAttempts += 1;
|
||||
const delayMs = computeBackoff(reconnectPolicy, reconnectAttempts);
|
||||
logReconnectVerbose(
|
||||
`Signal SSE stream ended, reconnecting in ${delayMs / 1000}s...`,
|
||||
);
|
||||
logReconnectVerbose(`Signal SSE stream ended, reconnecting in ${delayMs / 1000}s...`);
|
||||
await sleepWithAbort(delayMs, abortSignal);
|
||||
} catch (err) {
|
||||
if (abortSignal?.aborted) return;
|
||||
runtime.error?.(`Signal SSE stream error: ${String(err)}`);
|
||||
reconnectAttempts += 1;
|
||||
const delayMs = computeBackoff(reconnectPolicy, reconnectAttempts);
|
||||
runtime.log?.(
|
||||
`Signal SSE connection lost, reconnecting in ${delayMs / 1000}s...`,
|
||||
);
|
||||
runtime.log?.(`Signal SSE connection lost, reconnecting in ${delayMs / 1000}s...`);
|
||||
try {
|
||||
await sleepWithAbort(delayMs, abortSignal);
|
||||
} catch (sleepErr) {
|
||||
|
||||
Reference in New Issue
Block a user