Merge 303a19384a into 4b5514a259
This commit is contained in:
commit
047687391a
@ -6,6 +6,8 @@ Docs: https://docs.molt.bot
|
|||||||
Status: beta.
|
Status: beta.
|
||||||
|
|
||||||
### Changes
|
### Changes
|
||||||
|
- TUI: handle `/model status` and `/model list` subcommands instead of treating them as model names. (#3469) Thanks @riskatcher.
|
||||||
|
- WhatsApp: normalize literal `\n` escape sequences to actual newlines in outbound messages. (#3082)
|
||||||
- Rebrand: rename the npm package/CLI to `moltbot`, add a `moltbot` compatibility shim, and move extensions to the `@moltbot/*` scope.
|
- Rebrand: rename the npm package/CLI to `moltbot`, add a `moltbot` compatibility shim, and move extensions to the `@moltbot/*` scope.
|
||||||
- Commands: group /help and /commands output with Telegram paging. (#2504) Thanks @hougangdev.
|
- Commands: group /help and /commands output with Telegram paging. (#2504) Thanks @hougangdev.
|
||||||
- macOS: limit project-local `node_modules/.bin` PATH preference to debug builds (reduce PATH hijacking risk).
|
- macOS: limit project-local `node_modules/.bin` PATH preference to debug builds (reduce PATH hijacking risk).
|
||||||
|
|||||||
@ -15,7 +15,10 @@ import {
|
|||||||
shouldSkipDuplicateInbound,
|
shouldSkipDuplicateInbound,
|
||||||
} from "./reply/inbound-dedupe.js";
|
} from "./reply/inbound-dedupe.js";
|
||||||
import { formatInboundBodyWithSenderMeta } from "./reply/inbound-sender-meta.js";
|
import { formatInboundBodyWithSenderMeta } from "./reply/inbound-sender-meta.js";
|
||||||
import { normalizeInboundTextNewlines } from "./reply/inbound-text.js";
|
import {
|
||||||
|
normalizeInboundTextNewlines,
|
||||||
|
normalizeOutboundTextNewlines,
|
||||||
|
} from "./reply/inbound-text.js";
|
||||||
import { resolveGroupRequireMention } from "./reply/groups.js";
|
import { resolveGroupRequireMention } from "./reply/groups.js";
|
||||||
import {
|
import {
|
||||||
buildMentionRegexes,
|
buildMentionRegexes,
|
||||||
@ -69,6 +72,40 @@ describe("normalizeInboundTextNewlines", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("normalizeOutboundTextNewlines", () => {
|
||||||
|
it("keeps real newlines", () => {
|
||||||
|
expect(normalizeOutboundTextNewlines("a\nb")).toBe("a\nb");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("converts literal \\n to real newlines", () => {
|
||||||
|
expect(normalizeOutboundTextNewlines("Hello\\nWorld")).toBe("Hello\nWorld");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("converts multiple literal \\n sequences", () => {
|
||||||
|
expect(normalizeOutboundTextNewlines("Line1\\n\\nLine2")).toBe("Line1\n\nLine2");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("converts literal \\r\\n to real newlines", () => {
|
||||||
|
expect(normalizeOutboundTextNewlines("a\\r\\nb")).toBe("a\nb");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("converts literal \\r to real newlines", () => {
|
||||||
|
expect(normalizeOutboundTextNewlines("a\\rb")).toBe("a\nb");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("handles empty string", () => {
|
||||||
|
expect(normalizeOutboundTextNewlines("")).toBe("");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("handles text without escape sequences", () => {
|
||||||
|
expect(normalizeOutboundTextNewlines("Hello World")).toBe("Hello World");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("handles mixed real and literal newlines", () => {
|
||||||
|
expect(normalizeOutboundTextNewlines("Real\nand\\nliteral")).toBe("Real\nand\nliteral");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("finalizeInboundContext", () => {
|
describe("finalizeInboundContext", () => {
|
||||||
it("fills BodyForAgent/BodyForCommands and normalizes newlines", () => {
|
it("fills BodyForAgent/BodyForCommands and normalizes newlines", () => {
|
||||||
const ctx: MsgContext = {
|
const ctx: MsgContext = {
|
||||||
|
|||||||
@ -1,3 +1,17 @@
|
|||||||
export function normalizeInboundTextNewlines(input: string): string {
|
export function normalizeInboundTextNewlines(input: string): string {
|
||||||
return input.replaceAll("\r\n", "\n").replaceAll("\r", "\n").replaceAll("\\n", "\n");
|
return input.replaceAll("\r\n", "\n").replaceAll("\r", "\n").replaceAll("\\n", "\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normalize outbound text newlines before sending to channels.
|
||||||
|
* Converts literal `\n` escape sequences to actual newlines.
|
||||||
|
* Some LLM providers may output literal `\n` in their responses,
|
||||||
|
* which causes WhatsApp (and potentially other channels) to display
|
||||||
|
* the escape sequence as text instead of rendering a line break.
|
||||||
|
*/
|
||||||
|
export function normalizeOutboundTextNewlines(input: string): string {
|
||||||
|
if (!input) return input;
|
||||||
|
// Convert literal \n (backslash-n) sequences to actual newlines.
|
||||||
|
// Also handle \r\n and \r for consistency.
|
||||||
|
return input.replaceAll("\\r\\n", "\n").replaceAll("\\r", "\n").replaceAll("\\n", "\n");
|
||||||
|
}
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { chunkText } from "../../../auto-reply/chunk.js";
|
import { chunkText } from "../../../auto-reply/chunk.js";
|
||||||
|
import { normalizeOutboundTextNewlines } from "../../../auto-reply/reply/inbound-text.js";
|
||||||
import { shouldLogVerbose } from "../../../globals.js";
|
import { shouldLogVerbose } from "../../../globals.js";
|
||||||
import { sendPollWhatsApp } from "../../../web/outbound.js";
|
import { sendPollWhatsApp } from "../../../web/outbound.js";
|
||||||
import { isWhatsAppGroupJid, normalizeWhatsAppTarget } from "../../../whatsapp/normalize.js";
|
import { isWhatsAppGroupJid, normalizeWhatsAppTarget } from "../../../whatsapp/normalize.js";
|
||||||
@ -60,7 +61,8 @@ export const whatsappOutbound: ChannelOutboundAdapter = {
|
|||||||
sendText: async ({ to, text, accountId, deps, gifPlayback }) => {
|
sendText: async ({ to, text, accountId, deps, gifPlayback }) => {
|
||||||
const send =
|
const send =
|
||||||
deps?.sendWhatsApp ?? (await import("../../../web/outbound.js")).sendMessageWhatsApp;
|
deps?.sendWhatsApp ?? (await import("../../../web/outbound.js")).sendMessageWhatsApp;
|
||||||
const result = await send(to, text, {
|
const normalizedText = normalizeOutboundTextNewlines(text);
|
||||||
|
const result = await send(to, normalizedText, {
|
||||||
verbose: false,
|
verbose: false,
|
||||||
accountId: accountId ?? undefined,
|
accountId: accountId ?? undefined,
|
||||||
gifPlayback,
|
gifPlayback,
|
||||||
@ -70,7 +72,8 @@ export const whatsappOutbound: ChannelOutboundAdapter = {
|
|||||||
sendMedia: async ({ to, text, mediaUrl, accountId, deps, gifPlayback }) => {
|
sendMedia: async ({ to, text, mediaUrl, accountId, deps, gifPlayback }) => {
|
||||||
const send =
|
const send =
|
||||||
deps?.sendWhatsApp ?? (await import("../../../web/outbound.js")).sendMessageWhatsApp;
|
deps?.sendWhatsApp ?? (await import("../../../web/outbound.js")).sendMessageWhatsApp;
|
||||||
const result = await send(to, text, {
|
const normalizedText = normalizeOutboundTextNewlines(text);
|
||||||
|
const result = await send(to, normalizedText, {
|
||||||
verbose: false,
|
verbose: false,
|
||||||
mediaUrl,
|
mediaUrl,
|
||||||
accountId: accountId ?? undefined,
|
accountId: accountId ?? undefined,
|
||||||
|
|||||||
@ -278,6 +278,19 @@ export function createCommandHandlers(context: CommandHandlerContext) {
|
|||||||
case "model":
|
case "model":
|
||||||
if (!args) {
|
if (!args) {
|
||||||
await openModelSelector();
|
await openModelSelector();
|
||||||
|
} else if (args === "status") {
|
||||||
|
// Show current model information
|
||||||
|
const provider = state.sessionInfo.modelProvider ?? "unknown";
|
||||||
|
const model = state.sessionInfo.model ?? "unknown";
|
||||||
|
const current = `${provider}/${model}`;
|
||||||
|
chatLog.addSystem(
|
||||||
|
[`Current: ${current}`, "", "Switch: /model <provider/model>", "Browse: /models"].join(
|
||||||
|
"\n",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else if (args === "list") {
|
||||||
|
// /model list is an alias for /models
|
||||||
|
await openModelSelector();
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
await client.patchSession({
|
await client.patchSession({
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user