openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
372 lines (371 loc) โข 10.6 kB
JavaScript
import { c as normalizeOptionalLowercaseString } from "./string-coerce-1VuiLaaR.mjs";
import "./identity-DMes0XZ3.mjs";
import { i as shouldAckReaction } from "./ack-reactions-CBXgZ9bf.mjs";
import "./logging-C9LTVJNE.mjs";
//#region src/channels/status-reaction-tool-emojis.ts
const TOOL_REACTION_EMOJIS = /* @__PURE__ */ new Map([
["bash", "๐ ๏ธ"],
["computer", "๐ฑ๏ธ"],
["mobile_ui", "๐ฑ"],
["screen", "๐ฅ๏ธ"],
["theme", "๐จ"],
["terminal", "โจ๏ธ"],
["portal", "๐"],
["process", "๐งฐ"],
["gateway_process", "๐งฐ"],
["read", "๐"],
["write", "โ๏ธ"],
["edit", "๐"],
["personal_instructions", "๐"],
["presence", "๐งฉ"],
["attach", "๐"],
["api", "๐"],
["browser", "๐"],
["canvas", "๐ผ๏ธ"],
["dashboard", "๐"],
["nodes", "๐ฑ"],
["cron", "โฐ"],
["get_goal", "๐ฏ"],
["create_goal", "๐ฏ"],
["update_goal", "๐ฏ"],
["progress_card", "๐บ๏ธ"],
["ask_user", "โ"],
["secrets", "๐"],
["suggest_task", "โจ"],
["dismiss_task", "๐๏ธ"],
["skill_workshop", "๐งฐ"],
["openclaw", "๐ฆ"],
["gateway", "๐"],
["plugins", "๐งฉ"],
["exec", "๐ ๏ธ"],
["tool_call", "๐งฐ"],
["tool_call_update", "๐งฐ"],
["session_status", "๐"],
["github_publish", "๐"],
["github_identity_status", "๐"],
["sessions", "๐๏ธ"],
["sessions_list", "๐๏ธ"],
["conversations_list", "๐ฌ"],
["conversations_send", "๐จ"],
["conversations_turn", "โ๏ธ"],
["sessions_send", "๐จ"],
["sessions_history", "๐งพ"],
["sessions_search", "๐"],
["transcripts", "๐๏ธ"],
["sessions_spawn", "๐งโ๐ง"],
["agents_wait", "โณ"],
["structured_output", "๐งพ"],
["subagents", "๐ค"],
["agents_list", "๐งญ"],
["memory_search", "๐ง "],
["memory_get", "๐"],
["skills_search", "๐"],
["skills_read", "๐"],
["web_search", "๐"],
["web_fetch", "๐"],
["code_execution", "๐งฎ"],
["decision_evaluate", "โ๏ธ"],
["message", "โ๏ธ"],
["apply_patch", "๐ฉน"],
["image", "๐ผ๏ธ"],
["view_image", "๐ผ๏ธ"],
["image_generate", "๐จ"],
["music_generate", "๐ต"],
["video_generate", "๐ฌ"],
["pdf", "๐"],
["sessions_yield", "โธ๏ธ"],
["tts", "๐"]
]);
//#endregion
//#region src/channels/status-reactions.ts
const DEFAULT_EMOJIS = {
queued: "๐",
thinking: "๐ง ",
tool: "๐ ๏ธ",
coding: "๐ป",
web: "๐",
deploy: "๐ซ",
build: "๐๏ธ",
concierge: "๐",
done: "โ
",
error: "โ",
stallSoft: "โณ",
stallHard: "โ ๏ธ",
compacting: "๐๏ธ"
};
const DEFAULT_TIMING = {
debounceMs: 700,
stallSoftMs: 1e4,
stallHardMs: 3e4,
doneHoldMs: 1500,
errorHoldMs: 2500
};
const CODING_TOOL_TOKENS = [
"exec",
"process",
"read",
"write",
"edit",
"session_status",
"bash"
];
const WEB_TOOL_TOKENS = [
"web_search",
"web-search",
"web_fetch",
"web-fetch",
"browser"
];
const DEPLOY_TOOL_TOKENS = [
"fastlane",
"deploy",
"upload",
"testflight",
"ship",
"release",
"publish",
"distribute"
];
const BUILD_TOOL_TOKENS = [
"build",
"compile",
"xcode",
"swift",
"gradle",
"cargo",
"make",
"cmake",
"webpack",
"vite",
"tsc",
"lint"
];
const CONCIERGE_TOOL_TOKENS = [
"navigate",
"click",
"fill",
"screenshot",
"scroll",
"page",
"form",
"puppeteer",
"playwright",
"selenium",
"chromedp"
];
function resolveToolEmoji(toolName, emojis, emojiOverrides) {
const normalized = normalizeOptionalLowercaseString(toolName) ?? "";
if (!normalized) return emojis.tool;
const category = DEPLOY_TOOL_TOKENS.some((token) => normalized.includes(token)) ? "deploy" : BUILD_TOOL_TOKENS.some((token) => normalized.includes(token)) ? "build" : CONCIERGE_TOOL_TOKENS.some((token) => normalized.includes(token)) ? "concierge" : WEB_TOOL_TOKENS.some((token) => normalized.includes(token)) ? "web" : CODING_TOOL_TOKENS.some((token) => normalized.includes(token)) ? "coding" : "tool";
if (emojiOverrides?.[category] !== void 0) return emojis[category];
return TOOL_REACTION_EMOJIS.get(normalized) ?? emojis[category];
}
/** Defer reaction removal until cleanup to avoid flicker without atomic replacement. */
function createStatusReactionController(params) {
const { enabled, adapter, initialEmoji, onError } = params;
const showActivity = params.presentation !== "acknowledgement";
const emojis = {
...DEFAULT_EMOJIS,
queued: params.emojis?.queued ?? initialEmoji,
...params.emojis
};
const timing = {
...DEFAULT_TIMING,
...params.timing
};
let currentEmoji = "";
let pendingEmoji = "";
let debounceTimer = null;
let stallTimers = [];
let terminalHold = null;
let terminalHoldGeneration = 0;
let finished = false;
let chainPromise = Promise.resolve();
const activeEmojis = /* @__PURE__ */ new Set();
function enqueue(fn) {
chainPromise = chainPromise.then(fn, fn);
return chainPromise;
}
function clearActivityTimers() {
clearDebounceTimer();
clearStallTimers();
}
function clearStallTimers() {
for (const timer of stallTimers) clearTimeout(timer);
stallTimers = [];
}
function cancelTerminalHold() {
terminalHoldGeneration += 1;
const hold = terminalHold;
if (!hold) return;
terminalHold = null;
clearTimeout(hold.timer);
hold.resolve();
}
function waitForTerminalHold(holdMs, generation) {
if (holdMs <= 0 || generation !== terminalHoldGeneration) return Promise.resolve();
return new Promise((resolve) => {
terminalHold = {
timer: setTimeout(() => {
terminalHold = null;
resolve();
}, holdMs),
resolve
};
});
}
function clearDebounceTimer() {
if (debounceTimer) {
clearTimeout(debounceTimer);
debounceTimer = null;
}
}
function resetStallTimers() {
if (!showActivity) return;
clearStallTimers();
stallTimers = ["stallSoft", "stallHard"].map((phase) => setTimeout(() => {
scheduleEmoji(emojis[phase], {
immediate: true,
skipStallReset: true
});
}, timing[`${phase}Ms`]));
}
async function removeActiveEmojis(options = {}) {
if (!adapter.removeReaction) return;
for (const emoji of Array.from(activeEmojis)) {
if (emoji === options.keepEmoji) continue;
try {
await adapter.removeReaction(emoji);
} catch (err) {
onError?.(err);
} finally {
activeEmojis.delete(emoji);
}
}
}
async function applyEmoji(newEmoji) {
if (!enabled) return;
try {
if (!adapter.removeReaction || !activeEmojis.has(newEmoji)) await adapter.setReaction(newEmoji);
activeEmojis.add(newEmoji);
currentEmoji = newEmoji;
} catch (err) {
onError?.(err);
}
}
function scheduleEmoji(requestedEmoji, options = {}) {
if (!enabled || finished) return;
const emoji = showActivity ? requestedEmoji : initialEmoji;
if (emoji === currentEmoji || emoji === pendingEmoji) {
if (!options.skipStallReset) resetStallTimers();
return;
}
pendingEmoji = emoji;
clearDebounceTimer();
const applyPendingEmoji = async () => {
await applyEmoji(emoji);
pendingEmoji = "";
};
if (options.immediate) enqueue(applyPendingEmoji);
else debounceTimer = setTimeout(() => {
debounceTimer = null;
enqueue(applyPendingEmoji);
}, timing.debounceMs);
if (!options.skipStallReset) resetStallTimers();
}
function finishWithEmoji(emoji, holdMs) {
if (!enabled) return Promise.resolve();
finished = true;
clearActivityTimers();
const holdGeneration = terminalHoldGeneration;
return enqueue(async () => {
await applyEmoji(emoji);
await removeActiveEmojis({ keepEmoji: emoji });
pendingEmoji = "";
await waitForTerminalHold(holdMs, holdGeneration);
});
}
async function clear() {
if (!enabled) return;
clearActivityTimers();
cancelTerminalHold();
finished = true;
await enqueue(async () => {
if (adapter.clearReaction) try {
await adapter.clearReaction();
} catch (err) {
onError?.(err);
} finally {
activeEmojis.clear();
}
else if (adapter.removeReaction) await removeActiveEmojis();
currentEmoji = "";
pendingEmoji = "";
});
}
async function restoreInitial() {
if (!enabled) return;
const alreadyInitial = currentEmoji === initialEmoji;
const pendingBeforeClear = pendingEmoji;
const hadDebouncedPending = debounceTimer !== null;
const hasExtraActiveEmoji = Array.from(activeEmojis).some((emoji) => emoji !== initialEmoji);
clearActivityTimers();
if (!finished && alreadyInitial && (!pendingBeforeClear || hadDebouncedPending) && !hasExtraActiveEmoji) {
pendingEmoji = "";
return;
}
if (!finished && pendingBeforeClear === initialEmoji && !hadDebouncedPending) {
await chainPromise;
return;
}
await enqueue(async () => {
await applyEmoji(initialEmoji);
await removeActiveEmojis({ keepEmoji: initialEmoji });
pendingEmoji = "";
});
}
return {
setQueued: () => scheduleEmoji(emojis.queued, { immediate: true }),
setThinking: () => scheduleEmoji(emojis.thinking),
setTool: (toolName) => scheduleEmoji(resolveToolEmoji(toolName, emojis, params.emojis)),
setCompacting: () => scheduleEmoji(emojis.compacting),
cancelPending() {
clearDebounceTimer();
pendingEmoji = "";
},
setDone: () => showActivity ? finishWithEmoji(emojis.done, timing.doneHoldMs) : finishWithEmoji(initialEmoji, 0),
setError: () => finishWithEmoji(emojis.error, timing.errorHoldMs),
clear,
restoreInitial
};
}
//#endregion
//#region src/plugin-sdk/channel-feedback.ts
/**
* Public SDK subpath for channel feedback reactions, status reactions, and logging helpers.
*/
/**
* @deprecated Load-only bridge: the published WhatsApp channel package
* (2026.7.2-beta.7 and earlier) imports this at module top level, so removing
* it makes the installed plugin fail to load after a core upgrade. Behavior
* preserved verbatim from the pre-#121257 owner. Remove once managed releases
* have replaced the old npm latest/extended-stable packages and their upgrade
* window has closed.
*/
function shouldAckReactionForWhatsApp(params) {
if (!params.emoji) return false;
if (params.isDirect) return params.directEnabled;
if (!params.isGroup || params.groupMode === "never") return false;
if (params.groupMode === "always") return true;
return shouldAckReaction({
scope: "group-mentions",
isDirect: false,
isGroup: true,
isMentionableGroup: true,
canDetectMention: true,
effectiveWasMentioned: params.wasMentioned,
shouldBypassMention: params.groupActivated
});
}
//#endregion
export { DEFAULT_EMOJIS as a, WEB_TOOL_TOKENS as c, CONCIERGE_TOOL_TOKENS as i, createStatusReactionController as l, BUILD_TOOL_TOKENS as n, DEFAULT_TIMING as o, CODING_TOOL_TOKENS as r, DEPLOY_TOOL_TOKENS as s, shouldAckReactionForWhatsApp as t, resolveToolEmoji as u };