mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-03 22:50:26 +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:
@@ -26,6 +26,8 @@ import {
|
||||
resolveGoogleChatGroupToolPolicy,
|
||||
resolveIMessageGroupRequireMention,
|
||||
resolveIMessageGroupToolPolicy,
|
||||
resolveLineGroupRequireMention,
|
||||
resolveLineGroupToolPolicy,
|
||||
resolveSlackGroupRequireMention,
|
||||
resolveSlackGroupToolPolicy,
|
||||
resolveTelegramGroupRequireMention,
|
||||
@@ -547,6 +549,18 @@ const DOCKS: Record<ChatChannelId, ChannelDock> = {
|
||||
buildIMessageThreadToolContext({ context, hasRepliedRef }),
|
||||
},
|
||||
},
|
||||
line: {
|
||||
id: "line",
|
||||
capabilities: {
|
||||
chatTypes: ["direct", "group"],
|
||||
media: true,
|
||||
},
|
||||
outbound: { textChunkLimit: 5000 },
|
||||
groups: {
|
||||
resolveRequireMention: resolveLineGroupRequireMention,
|
||||
resolveToolPolicy: resolveLineGroupToolPolicy,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
function buildDockFromPlugin(plugin: ChannelPlugin): ChannelDock {
|
||||
|
||||
@@ -4,6 +4,8 @@ import {
|
||||
resolveBlueBubblesGroupToolPolicy,
|
||||
resolveDiscordGroupRequireMention,
|
||||
resolveDiscordGroupToolPolicy,
|
||||
resolveLineGroupRequireMention,
|
||||
resolveLineGroupToolPolicy,
|
||||
resolveSlackGroupRequireMention,
|
||||
resolveSlackGroupToolPolicy,
|
||||
resolveTelegramGroupRequireMention,
|
||||
@@ -208,3 +210,68 @@ describe("group mentions (bluebubbles)", () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("group mentions (line)", () => {
|
||||
it("matches raw and prefixed LINE group keys for requireMention and tools", () => {
|
||||
const lineCfg = {
|
||||
channels: {
|
||||
line: {
|
||||
groups: {
|
||||
"room:r123": {
|
||||
requireMention: false,
|
||||
tools: { allow: ["message.send"] },
|
||||
},
|
||||
"group:g123": {
|
||||
requireMention: false,
|
||||
tools: { deny: ["exec"] },
|
||||
},
|
||||
"*": {
|
||||
requireMention: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
// oxlint-disable-next-line typescript/no-explicit-any
|
||||
} as any;
|
||||
|
||||
expect(resolveLineGroupRequireMention({ cfg: lineCfg, groupId: "r123" })).toBe(false);
|
||||
expect(resolveLineGroupRequireMention({ cfg: lineCfg, groupId: "room:r123" })).toBe(false);
|
||||
expect(resolveLineGroupRequireMention({ cfg: lineCfg, groupId: "g123" })).toBe(false);
|
||||
expect(resolveLineGroupRequireMention({ cfg: lineCfg, groupId: "group:g123" })).toBe(false);
|
||||
expect(resolveLineGroupRequireMention({ cfg: lineCfg, groupId: "other" })).toBe(true);
|
||||
expect(resolveLineGroupToolPolicy({ cfg: lineCfg, groupId: "r123" })).toEqual({
|
||||
allow: ["message.send"],
|
||||
});
|
||||
expect(resolveLineGroupToolPolicy({ cfg: lineCfg, groupId: "g123" })).toEqual({
|
||||
deny: ["exec"],
|
||||
});
|
||||
});
|
||||
|
||||
it("uses account-scoped prefixed LINE group config for requireMention", () => {
|
||||
const lineCfg = {
|
||||
channels: {
|
||||
line: {
|
||||
groups: {
|
||||
"*": {
|
||||
requireMention: true,
|
||||
},
|
||||
},
|
||||
accounts: {
|
||||
work: {
|
||||
groups: {
|
||||
"group:g123": {
|
||||
requireMention: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
// oxlint-disable-next-line typescript/no-explicit-any
|
||||
} as any;
|
||||
|
||||
expect(
|
||||
resolveLineGroupRequireMention({ cfg: lineCfg, groupId: "g123", accountId: "work" }),
|
||||
).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -9,6 +9,7 @@ import type {
|
||||
GroupToolPolicyBySenderConfig,
|
||||
GroupToolPolicyConfig,
|
||||
} from "../../config/types.tools.js";
|
||||
import { resolveExactLineGroupConfigKey } from "../../line/group-keys.js";
|
||||
import { normalizeAtHashSlug, normalizeHyphenSlug } from "../../shared/string-normalization.js";
|
||||
import { inspectSlackAccount } from "../../slack/account-inspect.js";
|
||||
import type { ChannelGroupContext } from "./types.js";
|
||||
@@ -125,7 +126,8 @@ type ChannelGroupPolicyChannel =
|
||||
| "whatsapp"
|
||||
| "imessage"
|
||||
| "googlechat"
|
||||
| "bluebubbles";
|
||||
| "bluebubbles"
|
||||
| "line";
|
||||
|
||||
function resolveSlackChannelPolicyEntry(
|
||||
params: GroupMentionParams,
|
||||
@@ -322,3 +324,34 @@ export function resolveBlueBubblesGroupToolPolicy(
|
||||
): GroupToolPolicyConfig | undefined {
|
||||
return resolveChannelToolPolicyForSender(params, "bluebubbles");
|
||||
}
|
||||
|
||||
export function resolveLineGroupRequireMention(params: GroupMentionParams): boolean {
|
||||
const exactGroupId = resolveExactLineGroupConfigKey({
|
||||
cfg: params.cfg,
|
||||
accountId: params.accountId,
|
||||
groupId: params.groupId,
|
||||
});
|
||||
if (exactGroupId) {
|
||||
return resolveChannelGroupRequireMention({
|
||||
cfg: params.cfg,
|
||||
channel: "line",
|
||||
groupId: exactGroupId,
|
||||
accountId: params.accountId,
|
||||
});
|
||||
}
|
||||
return resolveChannelRequireMention(params, "line");
|
||||
}
|
||||
|
||||
export function resolveLineGroupToolPolicy(
|
||||
params: GroupMentionParams,
|
||||
): GroupToolPolicyConfig | undefined {
|
||||
const exactGroupId = resolveExactLineGroupConfigKey({
|
||||
cfg: params.cfg,
|
||||
accountId: params.accountId,
|
||||
groupId: params.groupId,
|
||||
});
|
||||
if (exactGroupId) {
|
||||
return resolveChannelToolPolicyForSender(params, "line", exactGroupId);
|
||||
}
|
||||
return resolveChannelToolPolicyForSender(params, "line");
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ export const CHAT_CHANNEL_ORDER = [
|
||||
"slack",
|
||||
"signal",
|
||||
"imessage",
|
||||
"line",
|
||||
] as const;
|
||||
|
||||
export type ChatChannelId = (typeof CHAT_CHANNEL_ORDER)[number];
|
||||
@@ -107,6 +108,16 @@ const CHAT_CHANNEL_META: Record<ChatChannelId, ChannelMeta> = {
|
||||
blurb: "this is still a work in progress.",
|
||||
systemImage: "message.fill",
|
||||
},
|
||||
line: {
|
||||
id: "line",
|
||||
label: "LINE",
|
||||
selectionLabel: "LINE (Messaging API)",
|
||||
detailLabel: "LINE Bot",
|
||||
docsPath: "/channels/line",
|
||||
docsLabel: "line",
|
||||
blurb: "LINE Messaging API webhook bot.",
|
||||
systemImage: "message",
|
||||
},
|
||||
};
|
||||
|
||||
export const CHAT_CHANNEL_ALIASES: Record<string, ChatChannelId> = {
|
||||
|
||||
Reference in New Issue
Block a user