94 lines
2.5 KiB
TypeScript
94 lines
2.5 KiB
TypeScript
import { getReplyFromConfig } from "../auto-reply/reply.js";
|
|
import { danger, success } from "../globals.js";
|
|
import { logInfo } from "../logger.js";
|
|
import { defaultRuntime, type RuntimeEnv } from "../runtime.js";
|
|
import { HEARTBEAT_PROMPT, stripHeartbeatToken } from "../web/auto-reply.js";
|
|
import { sendMessage } from "./send.js";
|
|
|
|
type ReplyResolver = typeof getReplyFromConfig;
|
|
|
|
export async function runTwilioHeartbeatOnce(opts: {
|
|
to: string;
|
|
verbose?: boolean;
|
|
runtime?: RuntimeEnv;
|
|
replyResolver?: ReplyResolver;
|
|
overrideBody?: string;
|
|
dryRun?: boolean;
|
|
}) {
|
|
const {
|
|
to,
|
|
verbose: _verbose = false,
|
|
runtime = defaultRuntime,
|
|
overrideBody,
|
|
dryRun = false,
|
|
} = opts;
|
|
const replyResolver = opts.replyResolver ?? getReplyFromConfig;
|
|
|
|
if (overrideBody && overrideBody.trim().length === 0) {
|
|
throw new Error("Override body must be non-empty when provided.");
|
|
}
|
|
|
|
try {
|
|
if (overrideBody) {
|
|
if (dryRun) {
|
|
logInfo(
|
|
`[dry-run] twilio send -> ${to}: ${overrideBody.trim()} (manual message)`,
|
|
runtime,
|
|
);
|
|
return;
|
|
}
|
|
await sendMessage(to, overrideBody, undefined, runtime);
|
|
logInfo(success(`sent manual message to ${to} (twilio)`), runtime);
|
|
return;
|
|
}
|
|
|
|
const replyResult = await replyResolver(
|
|
{
|
|
Body: HEARTBEAT_PROMPT,
|
|
From: to,
|
|
To: to,
|
|
MessageSid: undefined,
|
|
},
|
|
{ isHeartbeat: true },
|
|
);
|
|
|
|
const replyPayload = Array.isArray(replyResult)
|
|
? replyResult[0]
|
|
: replyResult;
|
|
|
|
if (
|
|
!replyPayload ||
|
|
(!replyPayload.text &&
|
|
!replyPayload.mediaUrl &&
|
|
!replyPayload.mediaUrls?.length)
|
|
) {
|
|
logInfo("heartbeat skipped: empty reply", runtime);
|
|
return;
|
|
}
|
|
|
|
const hasMedia = Boolean(
|
|
replyPayload.mediaUrl || (replyPayload.mediaUrls?.length ?? 0) > 0,
|
|
);
|
|
const stripped = stripHeartbeatToken(replyPayload.text);
|
|
if (stripped.shouldSkip && !hasMedia) {
|
|
logInfo(success("heartbeat: ok (HEARTBEAT_OK)"), runtime);
|
|
return;
|
|
}
|
|
|
|
const finalText = stripped.text || replyPayload.text || "";
|
|
if (dryRun) {
|
|
logInfo(
|
|
`[dry-run] heartbeat -> ${to}: ${finalText.slice(0, 200)}`,
|
|
runtime,
|
|
);
|
|
return;
|
|
}
|
|
|
|
await sendMessage(to, finalText, undefined, runtime);
|
|
logInfo(success(`heartbeat sent to ${to} (twilio)`), runtime);
|
|
} catch (err) {
|
|
runtime.error(danger(`Heartbeat failed: ${String(err)}`));
|
|
throw err;
|
|
}
|
|
}
|