mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 23:41:36 +00:00
refactor(reply): clarify explicit reply tags in off mode (#16189)
* refactor(reply): clarify explicit reply tags in off mode * fix(plugin-sdk): alias account-id subpath for extensions
This commit is contained in:
committed by
GitHub
parent
6f7d31c426
commit
ef70a55b7a
@@ -7,41 +7,54 @@ import { normalizeTargetForProvider } from "../../infra/outbound/target-normaliz
|
||||
import { extractReplyToTag } from "./reply-tags.js";
|
||||
import { createReplyToModeFilterForChannel } from "./reply-threading.js";
|
||||
|
||||
function resolveReplyThreadingForPayload(params: {
|
||||
payload: ReplyPayload;
|
||||
implicitReplyToId?: string;
|
||||
currentMessageId?: string;
|
||||
}): ReplyPayload {
|
||||
const implicitReplyToId = params.implicitReplyToId?.trim() || undefined;
|
||||
const currentMessageId = params.currentMessageId?.trim() || undefined;
|
||||
|
||||
// 1) Apply implicit reply threading first (replyToMode will strip later if needed).
|
||||
let resolved: ReplyPayload =
|
||||
params.payload.replyToId || params.payload.replyToCurrent === false || !implicitReplyToId
|
||||
? params.payload
|
||||
: { ...params.payload, replyToId: implicitReplyToId };
|
||||
|
||||
// 2) Parse explicit reply tags from text (if present) and clean them.
|
||||
if (typeof resolved.text === "string" && resolved.text.includes("[[")) {
|
||||
const { cleaned, replyToId, replyToCurrent, hasTag } = extractReplyToTag(
|
||||
resolved.text,
|
||||
currentMessageId,
|
||||
);
|
||||
resolved = {
|
||||
...resolved,
|
||||
text: cleaned ? cleaned : undefined,
|
||||
replyToId: replyToId ?? resolved.replyToId,
|
||||
replyToTag: hasTag || resolved.replyToTag,
|
||||
replyToCurrent: replyToCurrent || resolved.replyToCurrent,
|
||||
};
|
||||
}
|
||||
|
||||
// 3) If replyToCurrent was set out-of-band (e.g. tags already stripped upstream),
|
||||
// ensure replyToId is set to the current message id when available.
|
||||
if (resolved.replyToCurrent && !resolved.replyToId && currentMessageId) {
|
||||
resolved = {
|
||||
...resolved,
|
||||
replyToId: currentMessageId,
|
||||
};
|
||||
}
|
||||
|
||||
return resolved;
|
||||
}
|
||||
|
||||
// Backward-compatible helper: apply explicit reply tags/directives to a single payload.
|
||||
// This intentionally does not apply implicit threading.
|
||||
export function applyReplyTagsToPayload(
|
||||
payload: ReplyPayload,
|
||||
currentMessageId?: string,
|
||||
): ReplyPayload {
|
||||
if (typeof payload.text !== "string") {
|
||||
if (!payload.replyToCurrent || payload.replyToId) {
|
||||
return payload;
|
||||
}
|
||||
return {
|
||||
...payload,
|
||||
replyToId: currentMessageId?.trim() || undefined,
|
||||
};
|
||||
}
|
||||
const shouldParseTags = payload.text.includes("[[");
|
||||
if (!shouldParseTags) {
|
||||
if (!payload.replyToCurrent || payload.replyToId) {
|
||||
return payload;
|
||||
}
|
||||
return {
|
||||
...payload,
|
||||
replyToId: currentMessageId?.trim() || undefined,
|
||||
replyToTag: payload.replyToTag ?? true,
|
||||
};
|
||||
}
|
||||
const { cleaned, replyToId, replyToCurrent, hasTag } = extractReplyToTag(
|
||||
payload.text,
|
||||
currentMessageId,
|
||||
);
|
||||
return {
|
||||
...payload,
|
||||
text: cleaned ? cleaned : undefined,
|
||||
replyToId: replyToId ?? payload.replyToId,
|
||||
replyToTag: hasTag || payload.replyToTag,
|
||||
replyToCurrent: replyToCurrent || payload.replyToCurrent,
|
||||
};
|
||||
return resolveReplyThreadingForPayload({ payload, currentMessageId });
|
||||
}
|
||||
|
||||
export function isRenderablePayload(payload: ReplyPayload): boolean {
|
||||
@@ -64,13 +77,9 @@ export function applyReplyThreading(params: {
|
||||
const applyReplyToMode = createReplyToModeFilterForChannel(replyToMode, replyToChannel);
|
||||
const implicitReplyToId = currentMessageId?.trim() || undefined;
|
||||
return payloads
|
||||
.map((payload) => {
|
||||
const autoThreaded =
|
||||
payload.replyToId || payload.replyToCurrent === false || !implicitReplyToId
|
||||
? payload
|
||||
: { ...payload, replyToId: implicitReplyToId };
|
||||
return applyReplyTagsToPayload(autoThreaded, currentMessageId);
|
||||
})
|
||||
.map((payload) =>
|
||||
resolveReplyThreadingForPayload({ payload, implicitReplyToId, currentMessageId }),
|
||||
)
|
||||
.filter(isRenderablePayload)
|
||||
.map(applyReplyToMode);
|
||||
}
|
||||
|
||||
@@ -232,7 +232,7 @@ describe("createReplyToModeFilter", () => {
|
||||
});
|
||||
|
||||
it("keeps replyToId when mode is off and reply tags are allowed", () => {
|
||||
const filter = createReplyToModeFilter("off", { allowTagsWhenOff: true });
|
||||
const filter = createReplyToModeFilter("off", { allowExplicitReplyTagsWhenOff: true });
|
||||
expect(filter({ text: "hi", replyToId: "1", replyToTag: true }).replyToId).toBe("1");
|
||||
});
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ export function resolveReplyToMode(
|
||||
|
||||
export function createReplyToModeFilter(
|
||||
mode: ReplyToMode,
|
||||
opts: { allowTagsWhenOff?: boolean } = {},
|
||||
opts: { allowExplicitReplyTagsWhenOff?: boolean } = {},
|
||||
) {
|
||||
let hasThreaded = false;
|
||||
return (payload: ReplyPayload): ReplyPayload => {
|
||||
@@ -33,7 +33,8 @@ export function createReplyToModeFilter(
|
||||
return payload;
|
||||
}
|
||||
if (mode === "off") {
|
||||
if (opts.allowTagsWhenOff && payload.replyToTag) {
|
||||
const isExplicit = Boolean(payload.replyToTag) || Boolean(payload.replyToCurrent);
|
||||
if (opts.allowExplicitReplyTagsWhenOff && isExplicit) {
|
||||
return payload;
|
||||
}
|
||||
return { ...payload, replyToId: undefined };
|
||||
@@ -54,12 +55,15 @@ export function createReplyToModeFilterForChannel(
|
||||
channel?: OriginatingChannelType,
|
||||
) {
|
||||
const provider = normalizeChannelId(channel);
|
||||
// Always honour explicit [[reply_to_*]] tags even when replyToMode is "off".
|
||||
// Per-channel opt-out is possible but the safe default is to allow them.
|
||||
const allowTagsWhenOff = provider
|
||||
? (getChannelDock(provider)?.threading?.allowTagsWhenOff ?? true)
|
||||
: true;
|
||||
const normalized = typeof channel === "string" ? channel.trim().toLowerCase() : undefined;
|
||||
const isWebchat = normalized === "webchat";
|
||||
// Default: allow explicit reply tags/directives even when replyToMode is "off".
|
||||
// Unknown channels fail closed; internal webchat stays allowed.
|
||||
const dock = provider ? getChannelDock(provider) : undefined;
|
||||
const allowExplicitReplyTagsWhenOff = provider
|
||||
? (dock?.threading?.allowExplicitReplyTagsWhenOff ?? dock?.threading?.allowTagsWhenOff ?? true)
|
||||
: isWebchat;
|
||||
return createReplyToModeFilter(mode, {
|
||||
allowTagsWhenOff,
|
||||
allowExplicitReplyTagsWhenOff,
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user