auto: sync OpenClaw config 2026-09-09 16:13
This commit is contained in:
@@ -0,0 +1,216 @@
|
||||
"use strict";
|
||||
/**
|
||||
* Copyright (c) 2026 ByteDance Ltd. and/or its affiliates
|
||||
* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Abort trigger detection for the Lark/Feishu channel plugin.
|
||||
*
|
||||
* Provides a fast-path check to determine whether an inbound message is
|
||||
* an abort/stop command *before* it enters the per-chat serial queue.
|
||||
*
|
||||
* The trigger word list and normalisation logic are copied from the
|
||||
* OpenClaw core (`src/auto-reply/reply/abort.ts`) so the plugin can
|
||||
* make a lightweight decision without importing the full reply pipeline.
|
||||
* The message still flows through `tryFastAbortFromMessage()` for
|
||||
* authoritative handling.
|
||||
*/
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.isAbortTrigger = isAbortTrigger;
|
||||
exports.isLikelyAbortText = isLikelyAbortText;
|
||||
exports.isConversationStopIntent = isConversationStopIntent;
|
||||
exports.extractRawTextFromEvent = extractRawTextFromEvent;
|
||||
// ---------------------------------------------------------------------------
|
||||
// Trigger word list (synced with OpenClaw core abort.ts)
|
||||
// ---------------------------------------------------------------------------
|
||||
const ABORT_TRIGGERS = new Set([
|
||||
'stop',
|
||||
'esc',
|
||||
'abort',
|
||||
'wait',
|
||||
'exit',
|
||||
'interrupt',
|
||||
'detente',
|
||||
'deten',
|
||||
'detén',
|
||||
'arrete',
|
||||
'arrête',
|
||||
'停止',
|
||||
'やめて',
|
||||
'止めて',
|
||||
'रुको',
|
||||
'توقف',
|
||||
'стоп',
|
||||
'остановись',
|
||||
'останови',
|
||||
'остановить',
|
||||
'прекрати',
|
||||
'halt',
|
||||
'anhalten',
|
||||
'aufhören',
|
||||
'hoer auf',
|
||||
'stopp',
|
||||
'pare',
|
||||
'stop openclaw',
|
||||
'openclaw stop',
|
||||
'stop action',
|
||||
'stop current action',
|
||||
'stop run',
|
||||
'stop current run',
|
||||
'stop agent',
|
||||
'stop the agent',
|
||||
"stop don't do anything",
|
||||
'stop dont do anything',
|
||||
'stop do not do anything',
|
||||
'stop doing anything',
|
||||
'do not do that',
|
||||
'please stop',
|
||||
'stop please',
|
||||
]);
|
||||
// ---------------------------------------------------------------------------
|
||||
// Normalisation helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
const TRAILING_ABORT_PUNCTUATION_RE = /[.!?…,,。;;::'"'")\]}]+$/u;
|
||||
function normalizeAbortTriggerText(text) {
|
||||
return text
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.replace(/['`]/g, "'")
|
||||
.replace(/\s+/g, ' ')
|
||||
.replace(TRAILING_ABORT_PUNCTUATION_RE, '')
|
||||
.trim();
|
||||
}
|
||||
// ---------------------------------------------------------------------------
|
||||
// Public API
|
||||
// ---------------------------------------------------------------------------
|
||||
/** Exact trigger-word match (same logic as OpenClaw core `isAbortTrigger`). */
|
||||
function isAbortTrigger(text) {
|
||||
if (!text)
|
||||
return false;
|
||||
const normalized = normalizeAbortTriggerText(text);
|
||||
return ABORT_TRIGGERS.has(normalized);
|
||||
}
|
||||
/**
|
||||
* Extended abort detection: matches both bare trigger words and the
|
||||
* `/stop` command form. Used by the monitor fast-path.
|
||||
*/
|
||||
function isLikelyAbortText(text) {
|
||||
if (!text)
|
||||
return false;
|
||||
const trimmed = text.trim().toLowerCase();
|
||||
if (trimmed === '/stop')
|
||||
return true;
|
||||
return isAbortTrigger(trimmed);
|
||||
}
|
||||
// ---------------------------------------------------------------------------
|
||||
// Conversation stop-intent (broader than the exact abort triggers)
|
||||
// ---------------------------------------------------------------------------
|
||||
/**
|
||||
* Conversational "please stop / interrupt this exchange" phrases.
|
||||
*
|
||||
* Deliberately SEPARATE from {@link ABORT_TRIGGERS} (which is synced word-for-
|
||||
* word with OpenClaw core and matched by exact equality, e.g. `/stop`). These
|
||||
* are matched by substring so natural phrasings like "中断对话" or "stop
|
||||
* talking" are caught. The list is intentionally distinctive to avoid false
|
||||
* positives — a false positive only means we skip the deterministic peer-@
|
||||
* backstop for that turn (the model can still @ on its own), which is mild.
|
||||
*/
|
||||
const STOP_INTENT_PHRASES = [
|
||||
// zh — stop / terminate / pause
|
||||
'中断',
|
||||
'中止',
|
||||
'终止',
|
||||
'停止',
|
||||
'停下',
|
||||
'停一下',
|
||||
'暂停',
|
||||
'打住',
|
||||
'停手',
|
||||
'收手',
|
||||
// zh — "don't keep going / replying"
|
||||
'别聊',
|
||||
'别说了',
|
||||
'别回复',
|
||||
'别继续',
|
||||
'别再聊',
|
||||
'别再说',
|
||||
'别吵',
|
||||
'别争',
|
||||
'不要回复',
|
||||
'不要继续',
|
||||
'不用回复',
|
||||
'不用继续',
|
||||
// zh — "wrap up / be quiet"
|
||||
'结束对话',
|
||||
'结束讨论',
|
||||
'结束辩论',
|
||||
'到此为止',
|
||||
'闭嘴',
|
||||
// en
|
||||
'stop talking',
|
||||
'stop chatting',
|
||||
'stop debating',
|
||||
'stop the debate',
|
||||
'stop the conversation',
|
||||
'stop this conversation',
|
||||
'stop responding',
|
||||
'stop replying',
|
||||
'end the conversation',
|
||||
'end conversation',
|
||||
'end the debate',
|
||||
'shut up',
|
||||
'be quiet',
|
||||
'cut it out',
|
||||
'knock it off',
|
||||
'wrap it up',
|
||||
'stand down',
|
||||
];
|
||||
/**
|
||||
* Whether an inbound message expresses intent to stop / interrupt the ongoing
|
||||
* (bot-to-bot) exchange. Superset of {@link isLikelyAbortText} plus the
|
||||
* conversational phrases above.
|
||||
*
|
||||
* Two consumers: (1) suppress the deterministic peer-@ backstop so a stop
|
||||
* acknowledgement doesn't re-wake the peer bot; (2) mute an active bot loop so
|
||||
* the in-flight ping-pong drains instead of being re-armed. Substring match —
|
||||
* keep the list distinctive (no bare "停"/"stop") to limit false positives;
|
||||
* the worst case is a missed forced-@ or a self-healing mute (any normal
|
||||
* message lifts it).
|
||||
*/
|
||||
function isConversationStopIntent(text) {
|
||||
if (!text)
|
||||
return false;
|
||||
// Drop bot mention placeholders so "@Bot 中断对话" → "中断对话".
|
||||
const normalized = text.replace(/@_user_\d+/g, '').trim().toLowerCase();
|
||||
if (!normalized)
|
||||
return false;
|
||||
if (isLikelyAbortText(normalized))
|
||||
return true;
|
||||
return STOP_INTENT_PHRASES.some((p) => normalized.includes(p));
|
||||
}
|
||||
/**
|
||||
* Extract the raw text payload from a Feishu message event.
|
||||
*
|
||||
* Only handles `text` type messages. The `message.content` field is a
|
||||
* JSON string like `{"text":"hello"}`. Returns `undefined` for
|
||||
* non-text messages or parse failures.
|
||||
*
|
||||
* In group chats, bot mention placeholders (`@_user_N`) are stripped so
|
||||
* a message like `@Bot stop` is detected as `stop`.
|
||||
*/
|
||||
function extractRawTextFromEvent(event) {
|
||||
if (!event.message || event.message.message_type !== 'text') {
|
||||
return undefined;
|
||||
}
|
||||
try {
|
||||
const parsed = JSON.parse(event.message.content);
|
||||
let text = parsed?.text;
|
||||
if (typeof text !== 'string')
|
||||
return undefined;
|
||||
// Strip bot mention placeholders (@_user_1, @_user_2, etc.)
|
||||
text = text.replace(/@_user_\d+/g, '').trim();
|
||||
return text || undefined;
|
||||
}
|
||||
catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user