-
Notifications
You must be signed in to change notification settings - Fork 92
Expand file tree
/
Copy pathdynamic-agent.js
More file actions
121 lines (109 loc) · 3.96 KB
/
Copy pathdynamic-agent.js
File metadata and controls
121 lines (109 loc) · 3.96 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
/**
* 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.
*
* When running in multi-account mode the accountId is embedded as a
* namespace segment so each account's conversations stay isolated:
* default → wecom-dm-{peerId} (backward compatible)
* "sales" → wecom-sales-dm-{peerId}
*
* @param {string} chatType - "dm" or "group"
* @param {string} peerId - user id or group id
* @param {string} [accountId] - optional account namespace ("default" is omitted)
* @returns {string} agentId
*/
export function generateAgentId(chatType, peerId, accountId) {
const sanitizedId = String(peerId)
.toLowerCase()
.replace(/[^a-z0-9_-]/g, "_");
// Only embed the account prefix for non-default accounts so existing
// single-account deployments keep identical agent ids (zero breaking change).
const ns = accountId && accountId !== "default" ? `${accountId}-` : "";
if (chatType === "group") {
return `wecom-${ns}group-${sanitizedId}`;
}
return `wecom-${ns}dm-${sanitizedId}`;
}
/**
* Resolve runtime dynamic-agent settings from config.
*
* Accepts either the full openclaw config (legacy) or a per-account wecom
* config block directly (multi-account). Detection: if the object has
* `channels.wecom`, unwrap it; otherwise treat the object itself as the
* wecom account config.
*/
export function getDynamicAgentConfig(config) {
const wecom = config?.channels?.wecom ?? config ?? {};
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 || ["@"],
adminBypass: wecom.dynamicAgents?.adminBypass === true,
};
}
/**
* Decide whether this message context should route to a dynamic agent.
*/
export function shouldUseDynamicAgent({ chatType, config, senderIsAdmin = false }) {
const dynamicConfig = getDynamicAgentConfig(config);
if (!dynamicConfig.enabled) {
return false;
}
if (senderIsAdmin && dynamicConfig.adminBypass) {
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, "\\$&");
}