2026-01-30 03:15:10 +01:00
|
|
|
import type { OpenClawConfig } from "../config/types.js";
|
2026-01-11 02:17:10 +01:00
|
|
|
import {
|
2026-01-11 21:06:04 +05:30
|
|
|
type CommandNormalizeOptions,
|
2026-01-11 02:17:10 +01:00
|
|
|
listChatCommands,
|
|
|
|
|
listChatCommandsForConfig,
|
|
|
|
|
normalizeCommandBody,
|
|
|
|
|
} from "./commands-registry.js";
|
2026-01-15 07:07:37 +00:00
|
|
|
import { isAbortTrigger } from "./reply/abort.js";
|
2026-01-05 01:31:36 +01:00
|
|
|
|
2026-01-11 02:17:10 +01:00
|
|
|
export function hasControlCommand(
|
|
|
|
|
text?: string,
|
2026-01-30 03:15:10 +01:00
|
|
|
cfg?: OpenClawConfig,
|
2026-01-11 21:06:04 +05:30
|
|
|
options?: CommandNormalizeOptions,
|
2026-01-11 02:17:10 +01:00
|
|
|
): boolean {
|
2026-01-31 16:19:20 +09:00
|
|
|
if (!text) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2026-01-05 01:31:36 +01:00
|
|
|
const trimmed = text.trim();
|
2026-01-31 16:19:20 +09:00
|
|
|
if (!trimmed) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2026-01-11 21:06:04 +05:30
|
|
|
const normalizedBody = normalizeCommandBody(trimmed, options);
|
2026-01-31 16:19:20 +09:00
|
|
|
if (!normalizedBody) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2026-01-08 03:22:14 +01:00
|
|
|
const lowered = normalizedBody.toLowerCase();
|
2026-01-11 02:17:10 +01:00
|
|
|
const commands = cfg ? listChatCommandsForConfig(cfg) : listChatCommands();
|
|
|
|
|
for (const command of commands) {
|
2026-01-06 14:17:56 -06:00
|
|
|
for (const alias of command.textAliases) {
|
|
|
|
|
const normalized = alias.trim().toLowerCase();
|
2026-01-31 16:19:20 +09:00
|
|
|
if (!normalized) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if (lowered === normalized) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2026-01-06 14:17:56 -06:00
|
|
|
if (command.acceptsArgs && lowered.startsWith(normalized)) {
|
2026-01-08 03:22:14 +01:00
|
|
|
const nextChar = normalizedBody.charAt(normalized.length);
|
2026-01-31 16:19:20 +09:00
|
|
|
if (nextChar && /\s/.test(nextChar)) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2026-01-06 14:17:56 -06:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return false;
|
2026-01-05 01:31:36 +01:00
|
|
|
}
|
2026-01-15 07:07:37 +00:00
|
|
|
|
|
|
|
|
export function isControlCommandMessage(
|
|
|
|
|
text?: string,
|
2026-01-30 03:15:10 +01:00
|
|
|
cfg?: OpenClawConfig,
|
2026-01-15 07:07:37 +00:00
|
|
|
options?: CommandNormalizeOptions,
|
|
|
|
|
): boolean {
|
2026-01-31 16:19:20 +09:00
|
|
|
if (!text) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2026-01-15 07:07:37 +00:00
|
|
|
const trimmed = text.trim();
|
2026-01-31 16:19:20 +09:00
|
|
|
if (!trimmed) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
if (hasControlCommand(trimmed, cfg, options)) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2026-01-15 07:07:37 +00:00
|
|
|
const normalized = normalizeCommandBody(trimmed, options).trim().toLowerCase();
|
|
|
|
|
return isAbortTrigger(normalized);
|
|
|
|
|
}
|
2026-01-17 08:27:52 +00:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Coarse detection for inline directives/shortcuts (e.g. "hey /status") so channel monitors
|
|
|
|
|
* can decide whether to compute CommandAuthorized for a message.
|
|
|
|
|
*
|
|
|
|
|
* This intentionally errs on the side of false positives; CommandAuthorized only gates
|
|
|
|
|
* command/directive execution, not normal chat replies.
|
|
|
|
|
*/
|
|
|
|
|
export function hasInlineCommandTokens(text?: string): boolean {
|
|
|
|
|
const body = text ?? "";
|
2026-01-31 16:19:20 +09:00
|
|
|
if (!body.trim()) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2026-01-17 08:27:52 +00:00
|
|
|
return /(?:^|\s)[/!][a-z]/i.test(body);
|
|
|
|
|
}
|
2026-01-17 09:01:43 +00:00
|
|
|
|
|
|
|
|
export function shouldComputeCommandAuthorized(
|
|
|
|
|
text?: string,
|
2026-01-30 03:15:10 +01:00
|
|
|
cfg?: OpenClawConfig,
|
2026-01-17 09:01:43 +00:00
|
|
|
options?: CommandNormalizeOptions,
|
|
|
|
|
): boolean {
|
|
|
|
|
return isControlCommandMessage(text, cfg, options) || hasInlineCommandTokens(text);
|
|
|
|
|
}
|