refactor(logging): streamline whatsapp console output
This commit is contained in:
@@ -63,9 +63,7 @@ export async function startBrowserControlServerFromConfig(): Promise<BrowserServ
|
|||||||
resolved,
|
resolved,
|
||||||
};
|
};
|
||||||
|
|
||||||
logServer.info(
|
logServer.info(`Browser control listening on http://127.0.0.1:${port}/`);
|
||||||
`🦞 clawd browser control listening on http://127.0.0.1:${port}/`,
|
|
||||||
);
|
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -253,6 +253,14 @@ export function routeLogsToStderr(): void {
|
|||||||
forceConsoleToStderr = true;
|
forceConsoleToStderr = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const SUPPRESSED_CONSOLE_PREFIXES = ["Closing session:"] as const;
|
||||||
|
|
||||||
|
function shouldSuppressConsoleMessage(message: string): boolean {
|
||||||
|
return SUPPRESSED_CONSOLE_PREFIXES.some((prefix) =>
|
||||||
|
message.startsWith(prefix),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Route console.* calls through pino while still emitting to stdout/stderr.
|
* Route console.* calls through pino while still emitting to stdout/stderr.
|
||||||
* This keeps user-facing output unchanged but guarantees every console call is captured in log files.
|
* This keeps user-facing output unchanged but guarantees every console call is captured in log files.
|
||||||
@@ -282,6 +290,7 @@ export function enableConsoleCapture(): void {
|
|||||||
(level: Level, orig: (...args: unknown[]) => void) =>
|
(level: Level, orig: (...args: unknown[]) => void) =>
|
||||||
(...args: unknown[]) => {
|
(...args: unknown[]) => {
|
||||||
const formatted = util.format(...args);
|
const formatted = util.format(...args);
|
||||||
|
if (shouldSuppressConsoleMessage(formatted)) return;
|
||||||
try {
|
try {
|
||||||
// Map console levels to pino
|
// Map console levels to pino
|
||||||
if (level === "trace") {
|
if (level === "trace") {
|
||||||
@@ -339,10 +348,18 @@ function shouldLogToConsole(level: Level, settings: ConsoleSettings): boolean {
|
|||||||
|
|
||||||
type ChalkInstance = InstanceType<typeof Chalk>;
|
type ChalkInstance = InstanceType<typeof Chalk>;
|
||||||
|
|
||||||
|
function isRichConsoleEnv(): boolean {
|
||||||
|
const term = (process.env.TERM ?? "").toLowerCase();
|
||||||
|
if (process.env.COLORTERM || process.env.TERM_PROGRAM) return true;
|
||||||
|
return term.length > 0 && term !== "dumb";
|
||||||
|
}
|
||||||
|
|
||||||
function getColorForConsole(): ChalkInstance {
|
function getColorForConsole(): ChalkInstance {
|
||||||
if (process.env.NO_COLOR) return new Chalk({ level: 0 });
|
if (process.env.NO_COLOR) return new Chalk({ level: 0 });
|
||||||
const hasTty = Boolean(process.stdout.isTTY || process.stderr.isTTY);
|
const hasTty = Boolean(process.stdout.isTTY || process.stderr.isTTY);
|
||||||
return hasTty ? new Chalk({ level: 1 }) : new Chalk({ level: 0 });
|
return hasTty || isRichConsoleEnv()
|
||||||
|
? new Chalk({ level: 1 })
|
||||||
|
: new Chalk({ level: 0 });
|
||||||
}
|
}
|
||||||
|
|
||||||
const SUBSYSTEM_COLORS = [
|
const SUBSYSTEM_COLORS = [
|
||||||
|
|||||||
@@ -12,11 +12,10 @@ import {
|
|||||||
saveSessionStore,
|
saveSessionStore,
|
||||||
updateLastRoute,
|
updateLastRoute,
|
||||||
} from "../config/sessions.js";
|
} from "../config/sessions.js";
|
||||||
import { danger, isVerbose, logVerbose, success } from "../globals.js";
|
import { isVerbose, logVerbose } from "../globals.js";
|
||||||
import { emitHeartbeatEvent } from "../infra/heartbeat-events.js";
|
import { emitHeartbeatEvent } from "../infra/heartbeat-events.js";
|
||||||
import { enqueueSystemEvent } from "../infra/system-events.js";
|
import { enqueueSystemEvent } from "../infra/system-events.js";
|
||||||
import { logInfo } from "../logger.js";
|
import { createSubsystemLogger, getChildLogger } from "../logging.js";
|
||||||
import { getChildLogger } from "../logging.js";
|
|
||||||
import { getQueueSize } from "../process/command-queue.js";
|
import { getQueueSize } from "../process/command-queue.js";
|
||||||
import { defaultRuntime, type RuntimeEnv } from "../runtime.js";
|
import { defaultRuntime, type RuntimeEnv } from "../runtime.js";
|
||||||
import { isSelfChatMode, jidToE164, normalizeE164 } from "../utils.js";
|
import { isSelfChatMode, jidToE164, normalizeE164 } from "../utils.js";
|
||||||
@@ -38,6 +37,10 @@ import { formatError, getWebAuthAgeMs, readWebSelfId } from "./session.js";
|
|||||||
|
|
||||||
const WEB_TEXT_LIMIT = 4000;
|
const WEB_TEXT_LIMIT = 4000;
|
||||||
const DEFAULT_GROUP_HISTORY_LIMIT = 50;
|
const DEFAULT_GROUP_HISTORY_LIMIT = 50;
|
||||||
|
const whatsappLog = createSubsystemLogger("gateway/providers/whatsapp");
|
||||||
|
const whatsappInboundLog = whatsappLog.child("inbound");
|
||||||
|
const whatsappOutboundLog = whatsappLog.child("outbound");
|
||||||
|
const whatsappHeartbeatLog = whatsappLog.child("heartbeat");
|
||||||
|
|
||||||
let heartbeatsEnabled = true;
|
let heartbeatsEnabled = true;
|
||||||
export function setHeartbeatsEnabled(enabled: boolean) {
|
export function setHeartbeatsEnabled(enabled: boolean) {
|
||||||
@@ -209,7 +212,6 @@ export async function runWebHeartbeatOnce(opts: {
|
|||||||
to: string;
|
to: string;
|
||||||
verbose?: boolean;
|
verbose?: boolean;
|
||||||
replyResolver?: typeof getReplyFromConfig;
|
replyResolver?: typeof getReplyFromConfig;
|
||||||
runtime?: RuntimeEnv;
|
|
||||||
sender?: typeof sendMessageWhatsApp;
|
sender?: typeof sendMessageWhatsApp;
|
||||||
sessionId?: string;
|
sessionId?: string;
|
||||||
overrideBody?: string;
|
overrideBody?: string;
|
||||||
@@ -223,7 +225,6 @@ export async function runWebHeartbeatOnce(opts: {
|
|||||||
overrideBody,
|
overrideBody,
|
||||||
dryRun = false,
|
dryRun = false,
|
||||||
} = opts;
|
} = opts;
|
||||||
const _runtime = opts.runtime ?? defaultRuntime;
|
|
||||||
const replyResolver = opts.replyResolver ?? getReplyFromConfig;
|
const replyResolver = opts.replyResolver ?? getReplyFromConfig;
|
||||||
const sender = opts.sender ?? sendWithIpcFallback;
|
const sender = opts.sender ?? sendWithIpcFallback;
|
||||||
const runId = newConnectionId();
|
const runId = newConnectionId();
|
||||||
@@ -269,10 +270,8 @@ export async function runWebHeartbeatOnce(opts: {
|
|||||||
try {
|
try {
|
||||||
if (overrideBody) {
|
if (overrideBody) {
|
||||||
if (dryRun) {
|
if (dryRun) {
|
||||||
console.log(
|
whatsappHeartbeatLog.info(
|
||||||
success(
|
`[dry-run] web send -> ${to}: ${elide(overrideBody.trim(), 200)} (manual message)`,
|
||||||
`[dry-run] web send -> ${to}: ${overrideBody.trim()} (manual message)`,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -292,10 +291,8 @@ export async function runWebHeartbeatOnce(opts: {
|
|||||||
},
|
},
|
||||||
"manual heartbeat message sent",
|
"manual heartbeat message sent",
|
||||||
);
|
);
|
||||||
console.log(
|
whatsappHeartbeatLog.info(
|
||||||
success(
|
`manual heartbeat sent to ${to} (id ${sendResult.messageId})`,
|
||||||
`sent manual message to ${to} (web), id ${sendResult.messageId}`,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -328,7 +325,9 @@ export async function runWebHeartbeatOnce(opts: {
|
|||||||
},
|
},
|
||||||
"heartbeat skipped",
|
"heartbeat skipped",
|
||||||
);
|
);
|
||||||
if (verbose) console.log(success("heartbeat: ok (empty reply)"));
|
if (isVerbose()) {
|
||||||
|
whatsappHeartbeatLog.debug("heartbeat ok (empty reply)");
|
||||||
|
}
|
||||||
emitHeartbeatEvent({ status: "ok-empty", to });
|
emitHeartbeatEvent({ status: "ok-empty", to });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -350,7 +349,9 @@ export async function runWebHeartbeatOnce(opts: {
|
|||||||
{ to, reason: "heartbeat-token", rawLength: replyPayload.text?.length },
|
{ to, reason: "heartbeat-token", rawLength: replyPayload.text?.length },
|
||||||
"heartbeat skipped",
|
"heartbeat skipped",
|
||||||
);
|
);
|
||||||
console.log(success("heartbeat: ok (HEARTBEAT_OK)"));
|
if (isVerbose()) {
|
||||||
|
whatsappHeartbeatLog.debug("heartbeat ok (HEARTBEAT_OK)");
|
||||||
|
}
|
||||||
emitHeartbeatEvent({ status: "ok-token", to });
|
emitHeartbeatEvent({ status: "ok-token", to });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -368,8 +369,8 @@ export async function runWebHeartbeatOnce(opts: {
|
|||||||
{ to, reason: "dry-run", chars: finalText.length },
|
{ to, reason: "dry-run", chars: finalText.length },
|
||||||
"heartbeat dry-run",
|
"heartbeat dry-run",
|
||||||
);
|
);
|
||||||
console.log(
|
whatsappHeartbeatLog.info(
|
||||||
success(`[dry-run] heartbeat -> ${to}: ${finalText.slice(0, 200)}`),
|
`[dry-run] heartbeat -> ${to}: ${elide(finalText, 200)}`,
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -390,11 +391,11 @@ export async function runWebHeartbeatOnce(opts: {
|
|||||||
},
|
},
|
||||||
"heartbeat sent",
|
"heartbeat sent",
|
||||||
);
|
);
|
||||||
console.log(success(`heartbeat: alert sent to ${to}`));
|
whatsappHeartbeatLog.info(`heartbeat alert sent to ${to}`);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const reason = String(err);
|
const reason = String(err);
|
||||||
heartbeatLogger.warn({ to, error: reason }, "heartbeat failed");
|
heartbeatLogger.warn({ to, error: reason }, "heartbeat failed");
|
||||||
console.log(danger(`heartbeat: failed - ${reason}`));
|
whatsappHeartbeatLog.warn(`heartbeat failed (${reason})`);
|
||||||
emitHeartbeatEvent({ status: "failed", to, reason: String(err) });
|
emitHeartbeatEvent({ status: "failed", to, reason: String(err) });
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
@@ -516,7 +517,6 @@ async function deliverWebReply(params: {
|
|||||||
msg: WebInboundMsg;
|
msg: WebInboundMsg;
|
||||||
maxMediaBytes: number;
|
maxMediaBytes: number;
|
||||||
replyLogger: ReturnType<typeof getChildLogger>;
|
replyLogger: ReturnType<typeof getChildLogger>;
|
||||||
runtime: RuntimeEnv;
|
|
||||||
connectionId?: string;
|
connectionId?: string;
|
||||||
skipLog?: boolean;
|
skipLog?: boolean;
|
||||||
}) {
|
}) {
|
||||||
@@ -525,7 +525,6 @@ async function deliverWebReply(params: {
|
|||||||
msg,
|
msg,
|
||||||
maxMediaBytes,
|
maxMediaBytes,
|
||||||
replyLogger,
|
replyLogger,
|
||||||
runtime,
|
|
||||||
connectionId,
|
connectionId,
|
||||||
skipLog,
|
skipLog,
|
||||||
} = params;
|
} = params;
|
||||||
@@ -576,9 +575,9 @@ async function deliverWebReply(params: {
|
|||||||
await sendWithRetry(() => msg.reply(chunk), "text");
|
await sendWithRetry(() => msg.reply(chunk), "text");
|
||||||
}
|
}
|
||||||
if (!skipLog) {
|
if (!skipLog) {
|
||||||
logInfo(
|
const durationMs = Date.now() - replyStarted;
|
||||||
`✅ Sent web reply to ${msg.from} (${(Date.now() - replyStarted).toFixed(0)}ms)`,
|
whatsappOutboundLog.info(
|
||||||
runtime,
|
`Sent reply to ${msg.from} (${durationMs.toFixed(0)}ms)`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
replyLogger.info(
|
replyLogger.info(
|
||||||
@@ -659,9 +658,8 @@ async function deliverWebReply(params: {
|
|||||||
"media:document",
|
"media:document",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
logInfo(
|
whatsappOutboundLog.info(
|
||||||
`✅ Sent web media reply to ${msg.from} (${(media.buffer.length / (1024 * 1024)).toFixed(2)}MB)`,
|
`Sent media reply to ${msg.from} (${(media.buffer.length / (1024 * 1024)).toFixed(2)}MB)`,
|
||||||
runtime,
|
|
||||||
);
|
);
|
||||||
replyLogger.info(
|
replyLogger.info(
|
||||||
{
|
{
|
||||||
@@ -678,8 +676,8 @@ async function deliverWebReply(params: {
|
|||||||
"auto-reply sent (media)",
|
"auto-reply sent (media)",
|
||||||
);
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(
|
whatsappOutboundLog.error(
|
||||||
danger(`Failed sending web media to ${msg.from}: ${String(err)}`),
|
`Failed sending web media to ${msg.from}: ${String(err)}`,
|
||||||
);
|
);
|
||||||
replyLogger.warn({ err, mediaUrl }, "failed to send web media reply");
|
replyLogger.warn({ err, mediaUrl }, "failed to send web media reply");
|
||||||
if (index === 0) {
|
if (index === 0) {
|
||||||
@@ -693,7 +691,9 @@ async function deliverWebReply(params: {
|
|||||||
].filter(Boolean);
|
].filter(Boolean);
|
||||||
const fallbackText = fallbackTextParts.join("\n");
|
const fallbackText = fallbackTextParts.join("\n");
|
||||||
if (fallbackText) {
|
if (fallbackText) {
|
||||||
console.log(`⚠️ Media skipped; sent text-only to ${msg.from}`);
|
whatsappOutboundLog.warn(
|
||||||
|
`Media skipped; sent text-only to ${msg.from}`,
|
||||||
|
);
|
||||||
await msg.reply(fallbackText);
|
await msg.reply(fallbackText);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -892,13 +892,14 @@ export async function monitorWebProvider(
|
|||||||
"inbound web message",
|
"inbound web message",
|
||||||
);
|
);
|
||||||
|
|
||||||
const tsDisplay = msg.timestamp
|
|
||||||
? new Date(msg.timestamp).toISOString()
|
|
||||||
: new Date().toISOString();
|
|
||||||
const fromDisplay = msg.chatType === "group" ? conversationId : msg.from;
|
const fromDisplay = msg.chatType === "group" ? conversationId : msg.from;
|
||||||
console.log(
|
const kindLabel = msg.mediaType ? `, ${msg.mediaType}` : "";
|
||||||
`\n[${tsDisplay}] ${fromDisplay} -> ${msg.to}: ${combinedBody}`,
|
whatsappInboundLog.info(
|
||||||
|
`Inbound message ${fromDisplay} -> ${msg.to} (${msg.chatType}${kindLabel}, ${combinedBody.length} chars)`,
|
||||||
);
|
);
|
||||||
|
if (isVerbose()) {
|
||||||
|
whatsappInboundLog.debug(`Inbound body: ${elide(combinedBody, 400)}`);
|
||||||
|
}
|
||||||
|
|
||||||
if (msg.chatType !== "group") {
|
if (msg.chatType !== "group") {
|
||||||
const sessionCfg = cfg.inbound?.session;
|
const sessionCfg = cfg.inbound?.session;
|
||||||
@@ -957,7 +958,6 @@ export async function monitorWebProvider(
|
|||||||
msg,
|
msg,
|
||||||
maxMediaBytes,
|
maxMediaBytes,
|
||||||
replyLogger,
|
replyLogger,
|
||||||
runtime,
|
|
||||||
connectionId,
|
connectionId,
|
||||||
skipLog: true,
|
skipLog: true,
|
||||||
});
|
});
|
||||||
@@ -970,12 +970,8 @@ export async function monitorWebProvider(
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
console.error(
|
whatsappOutboundLog.error(
|
||||||
danger(
|
`Failed sending web tool update to ${msg.from ?? conversationId}: ${String(err)}`,
|
||||||
`Failed sending web tool update to ${msg.from ?? conversationId}: ${String(
|
|
||||||
err,
|
|
||||||
)}`,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -1031,7 +1027,6 @@ export async function monitorWebProvider(
|
|||||||
msg,
|
msg,
|
||||||
maxMediaBytes,
|
maxMediaBytes,
|
||||||
replyLogger,
|
replyLogger,
|
||||||
runtime,
|
|
||||||
connectionId,
|
connectionId,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1049,24 +1044,24 @@ export async function monitorWebProvider(
|
|||||||
|
|
||||||
const fromDisplay =
|
const fromDisplay =
|
||||||
msg.chatType === "group" ? conversationId : (msg.from ?? "unknown");
|
msg.chatType === "group" ? conversationId : (msg.from ?? "unknown");
|
||||||
|
const hasMedia = Boolean(
|
||||||
|
replyPayload.mediaUrl || replyPayload.mediaUrls?.length,
|
||||||
|
);
|
||||||
|
whatsappOutboundLog.info(
|
||||||
|
`Auto-replied to ${fromDisplay}${hasMedia ? " (media)" : ""}`,
|
||||||
|
);
|
||||||
if (isVerbose()) {
|
if (isVerbose()) {
|
||||||
console.log(
|
const preview =
|
||||||
success(
|
replyPayload.text != null
|
||||||
`↩️ Auto-replied to ${fromDisplay} (web${replyPayload.mediaUrl || replyPayload.mediaUrls?.length ? ", media" : ""})`,
|
? elide(replyPayload.text, 400)
|
||||||
),
|
: "<media>";
|
||||||
);
|
whatsappOutboundLog.debug(
|
||||||
} else {
|
`Reply body: ${preview}${hasMedia ? " (media)" : ""}`,
|
||||||
console.log(
|
|
||||||
success(
|
|
||||||
`↩️ ${replyPayload.text ?? "<media>"}${replyPayload.mediaUrl || replyPayload.mediaUrls?.length ? " (media)" : ""}`,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(
|
whatsappOutboundLog.error(
|
||||||
danger(
|
`Failed sending web auto-reply to ${msg.from ?? conversationId}: ${String(err)}`,
|
||||||
`Failed sending web auto-reply to ${msg.from ?? conversationId}: ${String(err)}`,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1090,7 +1085,9 @@ export async function monitorWebProvider(
|
|||||||
|
|
||||||
// Skip if this is a message we just sent (echo detection)
|
// Skip if this is a message we just sent (echo detection)
|
||||||
if (recentlySent.has(msg.body)) {
|
if (recentlySent.has(msg.body)) {
|
||||||
console.log(`⏭️ Skipping echo: detected recently sent message`);
|
whatsappInboundLog.debug(
|
||||||
|
"Skipping echo: detected recently sent message",
|
||||||
|
);
|
||||||
logVerbose(
|
logVerbose(
|
||||||
`Skipping auto-reply: detected echo (message matches recently sent text)`,
|
`Skipping auto-reply: detected echo (message matches recently sent text)`,
|
||||||
);
|
);
|
||||||
@@ -1211,8 +1208,8 @@ export async function monitorWebProvider(
|
|||||||
},
|
},
|
||||||
"Message timeout detected - forcing reconnect",
|
"Message timeout detected - forcing reconnect",
|
||||||
);
|
);
|
||||||
console.error(
|
whatsappHeartbeatLog.warn(
|
||||||
`⚠️ No messages received in ${minutesSinceLastMessage}m - restarting connection`,
|
`No messages received in ${minutesSinceLastMessage}m - restarting connection`,
|
||||||
);
|
);
|
||||||
closeListener(); // Trigger reconnect
|
closeListener(); // Trigger reconnect
|
||||||
}
|
}
|
||||||
@@ -1231,7 +1228,9 @@ export async function monitorWebProvider(
|
|||||||
{ connectionId, reason: "requests-in-flight", queued },
|
{ connectionId, reason: "requests-in-flight", queued },
|
||||||
"reply heartbeat skipped",
|
"reply heartbeat skipped",
|
||||||
);
|
);
|
||||||
console.log(success("heartbeat: skipped (requests in flight)"));
|
if (isVerbose()) {
|
||||||
|
whatsappHeartbeatLog.debug("heartbeat skipped (requests in flight)");
|
||||||
|
}
|
||||||
return { status: "skipped", reason: "requests-in-flight" };
|
return { status: "skipped", reason: "requests-in-flight" };
|
||||||
}
|
}
|
||||||
if (!replyHeartbeatMinutes) {
|
if (!replyHeartbeatMinutes) {
|
||||||
@@ -1260,7 +1259,9 @@ export async function monitorWebProvider(
|
|||||||
},
|
},
|
||||||
"reply heartbeat skipped",
|
"reply heartbeat skipped",
|
||||||
);
|
);
|
||||||
console.log(success("heartbeat: skipped (no recent inbound)"));
|
if (isVerbose()) {
|
||||||
|
whatsappHeartbeatLog.debug("heartbeat skipped (no recent inbound)");
|
||||||
|
}
|
||||||
return { status: "skipped", reason: "no-recent-inbound" };
|
return { status: "skipped", reason: "no-recent-inbound" };
|
||||||
}
|
}
|
||||||
const snapshot = getSessionSnapshot(cfg, fallbackTo, true);
|
const snapshot = getSessionSnapshot(cfg, fallbackTo, true);
|
||||||
@@ -1269,7 +1270,11 @@ export async function monitorWebProvider(
|
|||||||
{ connectionId, to: fallbackTo, reason: "no-session-for-fallback" },
|
{ connectionId, to: fallbackTo, reason: "no-session-for-fallback" },
|
||||||
"reply heartbeat skipped",
|
"reply heartbeat skipped",
|
||||||
);
|
);
|
||||||
console.log(success("heartbeat: skipped (no session to resume)"));
|
if (isVerbose()) {
|
||||||
|
whatsappHeartbeatLog.debug(
|
||||||
|
"heartbeat skipped (no session to resume)",
|
||||||
|
);
|
||||||
|
}
|
||||||
return { status: "skipped", reason: "no-session-for-fallback" };
|
return { status: "skipped", reason: "no-session-for-fallback" };
|
||||||
}
|
}
|
||||||
if (isVerbose()) {
|
if (isVerbose()) {
|
||||||
@@ -1289,7 +1294,6 @@ export async function monitorWebProvider(
|
|||||||
to: fallbackTo,
|
to: fallbackTo,
|
||||||
verbose,
|
verbose,
|
||||||
replyResolver,
|
replyResolver,
|
||||||
runtime,
|
|
||||||
sessionId: snapshot.entry.sessionId,
|
sessionId: snapshot.entry.sessionId,
|
||||||
});
|
});
|
||||||
heartbeatLogger.info(
|
heartbeatLogger.info(
|
||||||
@@ -1353,7 +1357,9 @@ export async function monitorWebProvider(
|
|||||||
},
|
},
|
||||||
"reply heartbeat skipped",
|
"reply heartbeat skipped",
|
||||||
);
|
);
|
||||||
console.log(success("heartbeat: ok (empty reply)"));
|
if (isVerbose()) {
|
||||||
|
whatsappHeartbeatLog.debug("heartbeat ok (empty reply)");
|
||||||
|
}
|
||||||
return { status: "ran", durationMs: Date.now() - started };
|
return { status: "ran", durationMs: Date.now() - started };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1371,7 +1377,9 @@ export async function monitorWebProvider(
|
|||||||
},
|
},
|
||||||
"reply heartbeat skipped",
|
"reply heartbeat skipped",
|
||||||
);
|
);
|
||||||
console.log(success("heartbeat: ok (HEARTBEAT_OK)"));
|
if (isVerbose()) {
|
||||||
|
whatsappHeartbeatLog.debug("heartbeat ok (HEARTBEAT_OK)");
|
||||||
|
}
|
||||||
return { status: "ran", durationMs: Date.now() - started };
|
return { status: "ran", durationMs: Date.now() - started };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1396,13 +1404,13 @@ export async function monitorWebProvider(
|
|||||||
msg: heartbeatInboundMsg,
|
msg: heartbeatInboundMsg,
|
||||||
maxMediaBytes,
|
maxMediaBytes,
|
||||||
replyLogger,
|
replyLogger,
|
||||||
runtime,
|
|
||||||
connectionId,
|
connectionId,
|
||||||
});
|
});
|
||||||
|
|
||||||
const durationMs = Date.now() - tickStart;
|
const durationMs = Date.now() - tickStart;
|
||||||
const summary = `heartbeat: alert sent (${formatDuration(durationMs)})`;
|
whatsappHeartbeatLog.info(
|
||||||
console.log(summary);
|
`heartbeat alert sent (${formatDuration(durationMs)})`,
|
||||||
|
);
|
||||||
heartbeatLogger.info(
|
heartbeatLogger.info(
|
||||||
{
|
{
|
||||||
connectionId,
|
connectionId,
|
||||||
@@ -1423,8 +1431,8 @@ export async function monitorWebProvider(
|
|||||||
},
|
},
|
||||||
"reply heartbeat failed",
|
"reply heartbeat failed",
|
||||||
);
|
);
|
||||||
console.log(
|
whatsappHeartbeatLog.warn(
|
||||||
danger(`heartbeat: failed (${formatDuration(durationMs)})`),
|
`heartbeat failed (${formatDuration(durationMs)})`,
|
||||||
);
|
);
|
||||||
return { status: "failed", reason: String(err) };
|
return { status: "failed", reason: String(err) };
|
||||||
}
|
}
|
||||||
@@ -1443,9 +1451,8 @@ export async function monitorWebProvider(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
logInfo(
|
whatsappLog.info(
|
||||||
"📡 Listening for personal WhatsApp Web inbound messages. Leave this running; Ctrl+C to stop.",
|
"Listening for personal WhatsApp inbound messages. Ctrl+C to stop.",
|
||||||
runtime,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!keepAlive) {
|
if (!keepAlive) {
|
||||||
@@ -1516,9 +1523,7 @@ export async function monitorWebProvider(
|
|||||||
|
|
||||||
if (loggedOut) {
|
if (loggedOut) {
|
||||||
runtime.error(
|
runtime.error(
|
||||||
danger(
|
"WhatsApp session logged out. Run `clawdis login --provider web` to relink.",
|
||||||
"WhatsApp session logged out. Run `clawdis login --provider web` to relink.",
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
await closeListener();
|
await closeListener();
|
||||||
break;
|
break;
|
||||||
@@ -1541,9 +1546,7 @@ export async function monitorWebProvider(
|
|||||||
"web reconnect: max attempts reached; continuing in degraded mode",
|
"web reconnect: max attempts reached; continuing in degraded mode",
|
||||||
);
|
);
|
||||||
runtime.error(
|
runtime.error(
|
||||||
danger(
|
`WhatsApp Web reconnect: max attempts reached (${reconnectAttempts}/${reconnectPolicy.maxAttempts}). Stopping web monitoring.`,
|
||||||
`WhatsApp Web reconnect: max attempts reached (${reconnectAttempts}/${reconnectPolicy.maxAttempts}). Stopping web monitoring.`,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
await closeListener();
|
await closeListener();
|
||||||
break;
|
break;
|
||||||
@@ -1561,9 +1564,7 @@ export async function monitorWebProvider(
|
|||||||
"web reconnect: scheduling retry",
|
"web reconnect: scheduling retry",
|
||||||
);
|
);
|
||||||
runtime.error(
|
runtime.error(
|
||||||
danger(
|
`WhatsApp Web connection closed (status ${statusCode}). Retry ${reconnectAttempts}/${reconnectPolicy.maxAttempts || "∞"} in ${formatDuration(delay)}… (${errorStr})`,
|
||||||
`WhatsApp Web connection closed (status ${statusCode}). Retry ${reconnectAttempts}/${reconnectPolicy.maxAttempts || "∞"} in ${formatDuration(delay)}… (${errorStr})`,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
await closeListener();
|
await closeListener();
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import {
|
|||||||
|
|
||||||
import { loadConfig } from "../config/config.js";
|
import { loadConfig } from "../config/config.js";
|
||||||
import { isVerbose, logVerbose } from "../globals.js";
|
import { isVerbose, logVerbose } from "../globals.js";
|
||||||
import { getChildLogger } from "../logging.js";
|
import { createSubsystemLogger, getChildLogger } from "../logging.js";
|
||||||
import { saveMediaBuffer } from "../media/store.js";
|
import { saveMediaBuffer } from "../media/store.js";
|
||||||
import { isSelfChatMode, jidToE164, normalizeE164 } from "../utils.js";
|
import { isSelfChatMode, jidToE164, normalizeE164 } from "../utils.js";
|
||||||
import {
|
import {
|
||||||
@@ -57,6 +57,9 @@ export async function monitorWebInbox(options: {
|
|||||||
onMessage: (msg: WebInboundMessage) => Promise<void>;
|
onMessage: (msg: WebInboundMessage) => Promise<void>;
|
||||||
}) {
|
}) {
|
||||||
const inboundLogger = getChildLogger({ module: "web-inbound" });
|
const inboundLogger = getChildLogger({ module: "web-inbound" });
|
||||||
|
const inboundConsoleLog = createSubsystemLogger(
|
||||||
|
"gateway/providers/whatsapp",
|
||||||
|
).child("inbound");
|
||||||
const sock = await createWaSocket(false, options.verbose);
|
const sock = await createWaSocket(false, options.verbose);
|
||||||
await waitForWaConnection(sock);
|
await waitForWaConnection(sock);
|
||||||
let onCloseResolve: ((reason: WebListenerCloseReason) => void) | null = null;
|
let onCloseResolve: ((reason: WebListenerCloseReason) => void) | null = null;
|
||||||
@@ -259,10 +262,22 @@ export async function monitorWebInbox(options: {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
void task.catch((err) => {
|
void task.catch((err) => {
|
||||||
console.error("Failed handling inbound web message:", String(err));
|
inboundLogger.error(
|
||||||
|
{ error: String(err) },
|
||||||
|
"failed handling inbound web message",
|
||||||
|
);
|
||||||
|
inboundConsoleLog.error(
|
||||||
|
`Failed handling inbound web message: ${String(err)}`,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Failed handling inbound web message:", String(err));
|
inboundLogger.error(
|
||||||
|
{ error: String(err) },
|
||||||
|
"failed handling inbound web message",
|
||||||
|
);
|
||||||
|
inboundConsoleLog.error(
|
||||||
|
`Failed handling inbound web message: ${String(err)}`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
import { randomUUID } from "node:crypto";
|
import { randomUUID } from "node:crypto";
|
||||||
|
|
||||||
import { logInfo } from "../logger.js";
|
import { createSubsystemLogger, getChildLogger } from "../logging.js";
|
||||||
import { getChildLogger } from "../logging.js";
|
|
||||||
import { toWhatsappJid } from "../utils.js";
|
import { toWhatsappJid } from "../utils.js";
|
||||||
import { getActiveWebListener } from "./active-listener.js";
|
import { getActiveWebListener } from "./active-listener.js";
|
||||||
import { loadWebMedia } from "./media.js";
|
import { loadWebMedia } from "./media.js";
|
||||||
|
|
||||||
|
const outboundLog = createSubsystemLogger("gateway/providers/whatsapp").child(
|
||||||
|
"outbound",
|
||||||
|
);
|
||||||
|
|
||||||
export async function sendMessageWhatsApp(
|
export async function sendMessageWhatsApp(
|
||||||
to: string,
|
to: string,
|
||||||
body: string,
|
body: string,
|
||||||
@@ -13,6 +16,7 @@ export async function sendMessageWhatsApp(
|
|||||||
): Promise<{ messageId: string; toJid: string }> {
|
): Promise<{ messageId: string; toJid: string }> {
|
||||||
let text = body;
|
let text = body;
|
||||||
const correlationId = randomUUID();
|
const correlationId = randomUUID();
|
||||||
|
const startedAt = Date.now();
|
||||||
const active = getActiveWebListener();
|
const active = getActiveWebListener();
|
||||||
if (!active) {
|
if (!active) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
@@ -47,8 +51,8 @@ export async function sendMessageWhatsApp(
|
|||||||
text = caption ?? "";
|
text = caption ?? "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
logInfo(
|
outboundLog.info(
|
||||||
`📤 Sending via web session -> ${jid}${options.mediaUrl ? " (media)" : ""}`,
|
`Sending message -> ${jid}${options.mediaUrl ? " (media)" : ""}`,
|
||||||
);
|
);
|
||||||
logger.info(
|
logger.info(
|
||||||
{ jid, hasMedia: Boolean(options.mediaUrl) },
|
{ jid, hasMedia: Boolean(options.mediaUrl) },
|
||||||
@@ -59,8 +63,9 @@ export async function sendMessageWhatsApp(
|
|||||||
const result = await active.sendMessage(to, text, mediaBuffer, mediaType);
|
const result = await active.sendMessage(to, text, mediaBuffer, mediaType);
|
||||||
const messageId =
|
const messageId =
|
||||||
(result as { messageId?: string })?.messageId ?? "unknown";
|
(result as { messageId?: string })?.messageId ?? "unknown";
|
||||||
logInfo(
|
const durationMs = Date.now() - startedAt;
|
||||||
`✅ Sent via web session. Message ID: ${messageId} -> ${jid}${options.mediaUrl ? " (media)" : ""}`,
|
outboundLog.info(
|
||||||
|
`Sent message ${messageId} -> ${jid}${options.mediaUrl ? " (media)" : ""} (${durationMs}ms)`,
|
||||||
);
|
);
|
||||||
logger.info({ jid, messageId }, "sent message");
|
logger.info({ jid, messageId }, "sent message");
|
||||||
return { messageId, toJid: jid };
|
return { messageId, toJid: jid };
|
||||||
|
|||||||
Reference in New Issue
Block a user