Newer
Older
WeComCompanyPlugin / dynamic-agent.js
/**
 * Dynamic agent helpers.
 *
 * This plugin only computes deterministic agent ids/session keys.
 * Workspace/bootstrap creation is handled by OpenClaw core.
 */

/**
 * Build a deterministic agent id for dm/group contexts.
 *
 * @param {string} chatType - "dm" or "group"
 * @param {string} peerId - user id or group id
 * @returns {string} agentId
 */
export function generateAgentId(chatType, peerId) {
  const sanitizedId = String(peerId)
    .toLowerCase()
    .replace(/[^a-z0-9_-]/g, "_");
  if (chatType === "group") {
    return `wecom-group-${sanitizedId}`;
  }
  return `wecom-dm-${sanitizedId}`;
}

/**
 * Resolve runtime dynamic-agent settings from config.
 */
export function getDynamicAgentConfig(config) {
  const wecom = config?.channels?.wecom || {};
  return {
    enabled: wecom.dynamicAgents?.enabled !== false,
    dmCreateAgent: wecom.dm?.createAgentOnFirstMessage !== false,
    groupEnabled: wecom.groupChat?.enabled !== false,
    groupRequireMention: wecom.groupChat?.requireMention !== false,
    groupMentionPatterns: wecom.groupChat?.mentionPatterns || ["@"],
  };
}

/**
 * Decide whether this message context should route to a dynamic agent.
 */
export function shouldUseDynamicAgent({ chatType, config }) {
  const dynamicConfig = getDynamicAgentConfig(config);
  if (!dynamicConfig.enabled) {
    return false;
  }
  if (chatType === "group") {
    return dynamicConfig.groupEnabled;
  }
  return dynamicConfig.dmCreateAgent;
}

/**
 * Decide whether a group message should trigger a response.
 */
export function shouldTriggerGroupResponse(content, config) {
  const dynamicConfig = getDynamicAgentConfig(config);

  if (!dynamicConfig.groupEnabled) {
    return false;
  }

  if (!dynamicConfig.groupRequireMention) {
    return true;
  }

  // Match any configured mention marker in the original message content.
  // Use word-boundary check to avoid false positives on email addresses.
  const patterns = dynamicConfig.groupMentionPatterns;
  for (const pattern of patterns) {
    const escaped = escapeRegExp(pattern);
    // @ must NOT be preceded by a word char (avoids user@domain false matches).
    const re = new RegExp(`(?:^|(?<=\\s|[^\\w]))${escaped}`, "u");
    if (re.test(content)) {
      return true;
    }
  }

  return false;
}

/**
 * Remove configured mention markers from group message text.
 */
export function extractGroupMessageContent(content, config) {
  const dynamicConfig = getDynamicAgentConfig(config);
  let cleanContent = content;

  const patterns = dynamicConfig.groupMentionPatterns;
  for (const pattern of patterns) {
    const escapedPattern = escapeRegExp(pattern);
    // Only strip @name tokens that are NOT part of email-style addresses.
    // Require the pattern to be preceded by start-of-string or whitespace.
    const regex = new RegExp(`(?:^|(?<=\\s))${escapedPattern}\\S*\\s*`, "gu");
    cleanContent = cleanContent.replace(regex, "");
  }

  return cleanContent.trim();
}

function escapeRegExp(value) {
  return String(value).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}