mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-07 22:09:57 +00:00
Fix/Complete LINE requireMention gating behavior (#35847)
* fix(line): enforce requireMention gating in group message handler
* fix(line): scope canDetectMention to text messages, pass hasAnyMention
* fix(line): fix TS errors in mentionees type and test casts
* feat(line): register LINE in DOCKS and CHAT_CHANNEL_ORDER
- Add "line" to CHAT_CHANNEL_ORDER and CHAT_CHANNEL_META in registry.ts
- Export resolveLineGroupRequireMention and resolveLineGroupToolPolicy
in group-mentions.ts using the generic resolveChannelGroupRequireMention
and resolveChannelGroupToolsPolicy helpers (same pattern as iMessage)
- Add "line" entry to DOCKS in dock.ts so resolveGroupRequireMention
in the reply stage can correctly read LINE group config
Fixes the third layer of the requireMention bug: previously
getChannelDock("line") returned undefined, causing the reply-stage
resolveGroupRequireMention to fall back to true unconditionally.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(line): pending history, requireMention default, mentionPatterns fallback
- Default requireMention to true (consistent with other channels)
- Add mentionPatterns regex fallback alongside native isSelf/@all detection
- Record unmentioned group messages via recordPendingHistoryEntryIfEnabled
- Inject pending history context in buildLineMessageContext when bot is mentioned
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(line): update tests for requireMention default and pending history
- Add requireMention: false to 6 group tests unrelated to mention gating
(allowlist, replay dedup, inflight dedup, error retry) to preserve
their original intent after the default changed from false to true
- Add test: skips group messages by default when requireMention not configured
- Add test: records unmentioned group messages as pending history
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(line): use undefined instead of empty string as historyKey sentinel
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(line): deliver pending history via InboundHistory, not Body mutation
- Remove post-hoc ctxPayload.Body injection (BodyForAgent takes priority
in the prompt pipeline, so Body was never reached)
- Pass InboundHistory array to finalizeInboundContext instead, matching
the Telegram pattern rendered by buildInboundUserContextPrefix
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(line): pass agentId to buildMentionRegexes for per-agent mentionPatterns
- Resolve route before mention gating to obtain agentId
- Pass agentId to buildMentionRegexes, matching Telegram behavior
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(line): clear pending history after handled group turn
- Call clearHistoryEntriesIfEnabled after processMessage for group messages
- Prevents stale skipped messages from replaying on subsequent mentions
- Matches Discord, Signal, Slack, iMessage behavior
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* style(line): fix import order and merge orphaned JSDoc in bot-handlers
- Move resolveAgentRoute import from ./local group to ../routing group
- Merge duplicate JSDoc blocks above getLineMentionees into one
Addresses Greptile review comments r2888826724 and r2888826840 on PR #35847.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(line): read historyLimit from config and guard clear with has()
- bot.ts: resolve historyLimit from cfg.messages.groupChat.historyLimit
with fallback to DEFAULT_GROUP_HISTORY_LIMIT, so setting historyLimit: 0
actually disables pending history accumulation
- bot-handlers.ts: add groupHistories.has(historyKey) guard before
clearHistoryEntriesIfEnabled to prevent writing empty buckets for
groups that have never accumulated pending history (memory leak)
Addresses Codex review comments r2888829146 and r2888829152 on PR #35847.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* style(line): apply oxfmt formatting to bot-handlers and bot
Auto-formatted by oxfmt to fix CI format:check failure on PR #35847.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(line): add shouldLogVerbose to globals mock in bot-handlers test
resolveAgentRoute calls shouldLogVerbose() from globals.js; the mock
was missing this export, causing 13 test failures.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Address review findings for #35847
---------
Co-authored-by: Kaiyi <me@kaiyi.cool>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Yi-Cheng Wang <yicheng.wang@heph-ai.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import type { MessageEvent, StickerEventMessage, EventSource, PostbackEvent } from "@line/bot-sdk";
|
||||
import { formatInboundEnvelope } from "../auto-reply/envelope.js";
|
||||
import { type HistoryEntry } from "../auto-reply/reply/history.js";
|
||||
import { finalizeInboundContext } from "../auto-reply/reply/inbound-context.js";
|
||||
import { formatLocationText, toLocationContext } from "../channels/location.js";
|
||||
import { resolveInboundSessionEnvelopeContext } from "../channels/session-envelope.js";
|
||||
@@ -10,6 +11,7 @@ import { recordChannelActivity } from "../infra/channel-activity.js";
|
||||
import { resolveAgentRoute } from "../routing/resolve-route.js";
|
||||
import { resolvePinnedMainDmOwnerFromAllowlist } from "../security/dm-policy-shared.js";
|
||||
import { normalizeAllowFrom } from "./bot-access.js";
|
||||
import { resolveLineGroupConfigEntry, resolveLineGroupHistoryKey } from "./group-keys.js";
|
||||
import type { ResolvedLineAccount, LineGroupConfig } from "./types.js";
|
||||
|
||||
interface MediaRef {
|
||||
@@ -23,6 +25,8 @@ interface BuildLineMessageContextParams {
|
||||
cfg: OpenClawConfig;
|
||||
account: ResolvedLineAccount;
|
||||
commandAuthorized: boolean;
|
||||
groupHistories?: Map<string, HistoryEntry[]>;
|
||||
historyLimit?: number;
|
||||
}
|
||||
|
||||
export type LineSourceInfo = {
|
||||
@@ -49,11 +53,12 @@ export function getLineSourceInfo(source: EventSource): LineSourceInfo {
|
||||
}
|
||||
|
||||
function buildPeerId(source: EventSource): string {
|
||||
if (source.type === "group" && source.groupId) {
|
||||
return source.groupId;
|
||||
}
|
||||
if (source.type === "room" && source.roomId) {
|
||||
return source.roomId;
|
||||
const groupKey = resolveLineGroupHistoryKey({
|
||||
groupId: source.type === "group" ? source.groupId : undefined,
|
||||
roomId: source.type === "room" ? source.roomId : undefined,
|
||||
});
|
||||
if (groupKey) {
|
||||
return groupKey;
|
||||
}
|
||||
if (source.type === "user" && source.userId) {
|
||||
return source.userId;
|
||||
@@ -211,13 +216,10 @@ function resolveLineGroupSystemPrompt(
|
||||
groups: Record<string, LineGroupConfig | undefined> | undefined,
|
||||
source: LineSourceInfoWithPeerId,
|
||||
): string | undefined {
|
||||
if (!groups) {
|
||||
return undefined;
|
||||
}
|
||||
const entry =
|
||||
(source.groupId ? (groups[source.groupId] ?? groups[`group:${source.groupId}`]) : undefined) ??
|
||||
(source.roomId ? (groups[source.roomId] ?? groups[`room:${source.roomId}`]) : undefined) ??
|
||||
groups["*"];
|
||||
const entry = resolveLineGroupConfigEntry(groups, {
|
||||
groupId: source.groupId,
|
||||
roomId: source.roomId,
|
||||
});
|
||||
return entry?.systemPrompt?.trim() || undefined;
|
||||
}
|
||||
|
||||
@@ -239,6 +241,7 @@ async function finalizeLineInboundContext(params: {
|
||||
};
|
||||
locationContext?: ReturnType<typeof toLocationContext>;
|
||||
verboseLog: { kind: "inbound" | "postback"; mediaCount?: number };
|
||||
inboundHistory?: Pick<HistoryEntry, "sender" | "body" | "timestamp">[];
|
||||
}) {
|
||||
const { fromAddress, toAddress, originatingTo } = resolveLineAddresses({
|
||||
isGroup: params.source.isGroup,
|
||||
@@ -308,6 +311,7 @@ async function finalizeLineInboundContext(params: {
|
||||
GroupSystemPrompt: params.source.isGroup
|
||||
? resolveLineGroupSystemPrompt(params.account.config.groups, params.source)
|
||||
: undefined,
|
||||
InboundHistory: params.inboundHistory,
|
||||
});
|
||||
|
||||
const pinnedMainDmOwner = !params.source.isGroup
|
||||
@@ -362,7 +366,7 @@ async function finalizeLineInboundContext(params: {
|
||||
}
|
||||
|
||||
export async function buildLineMessageContext(params: BuildLineMessageContextParams) {
|
||||
const { event, allMedia, cfg, account, commandAuthorized } = params;
|
||||
const { event, allMedia, cfg, account, commandAuthorized, groupHistories, historyLimit } = params;
|
||||
|
||||
const source = event.source;
|
||||
const { userId, groupId, roomId, isGroup, peerId, route } = resolveLineInboundRoute({
|
||||
@@ -399,6 +403,19 @@ export async function buildLineMessageContext(params: BuildLineMessageContextPar
|
||||
});
|
||||
}
|
||||
|
||||
// Build pending history for group chats: unmentioned messages accumulated in
|
||||
// groupHistories are passed as InboundHistory so the agent has context about
|
||||
// the conversation that preceded the mention.
|
||||
const historyKey = isGroup ? peerId : undefined;
|
||||
const inboundHistory =
|
||||
historyKey && groupHistories && (historyLimit ?? 0) > 0
|
||||
? (groupHistories.get(historyKey) ?? []).map((entry) => ({
|
||||
sender: entry.sender,
|
||||
body: entry.body,
|
||||
timestamp: entry.timestamp,
|
||||
}))
|
||||
: undefined;
|
||||
|
||||
const { ctxPayload } = await finalizeLineInboundContext({
|
||||
cfg,
|
||||
account,
|
||||
@@ -420,6 +437,7 @@ export async function buildLineMessageContext(params: BuildLineMessageContextPar
|
||||
},
|
||||
locationContext,
|
||||
verboseLog: { kind: "inbound", mediaCount: allMedia.length },
|
||||
inboundHistory,
|
||||
});
|
||||
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user