@occum-net/occumclaw 0.2.2 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.ts CHANGED
@@ -74,8 +74,10 @@ interface MsgContext {
74
74
  AccountId?: string;
75
75
  Provider?: string;
76
76
  ChatType?: string;
77
+ GroupId?: string;
77
78
  SenderId?: string;
78
79
  SenderName?: string;
80
+ SenderUsername?: string;
79
81
  Timestamp?: number;
80
82
  CommandAuthorized?: boolean;
81
83
  MessageSid?: string;
@@ -142,6 +144,164 @@ interface OpenClawPluginApi {
142
144
  registerChannel(registration: { plugin: any }): void;
143
145
  }
144
146
 
147
+ // ─── Group Tool Policy (channel-level + per-sender overrides) ───────────────
148
+ //
149
+ // Follows the same resolution hierarchy as OpenClaw's built-in Telegram channel:
150
+ //
151
+ // 1. Group-specific sender override (groups.<channelId>.toolsBySender)
152
+ // 2. Group-specific tool policy (groups.<channelId>.tools)
153
+ // 3. Wildcard sender override (groups.*.toolsBySender)
154
+ // 4. Wildcard tool policy (groups.*.tools)
155
+ // 5. undefined → fall back to global (tools.allow / tools.profile)
156
+ //
157
+ // Config path (either works; plugin-style avoids config validation issues):
158
+ // channels.occum.accounts.<accountId>.groups.<channelId>.tools
159
+ // plugins.entries.occumclaw.config.groups.<channelId>.tools
160
+ //
161
+ // Per-sender key format:
162
+ // "id:<senderId>" — match by user/agent ID
163
+ // "username:<handle>" — match by username
164
+ // "name:<displayName>" — match by display name
165
+ // "*" — wildcard (any sender)
166
+
167
+ /** Tool policy: allow/alsoAllow/deny lists for a group or sender. */
168
+ interface GroupToolPolicyConfig {
169
+ allow?: string[];
170
+ alsoAllow?: string[];
171
+ deny?: string[];
172
+ }
173
+
174
+ /** Per-sender overrides keyed by "id:<val>", "username:<val>", "name:<val>", or "*". */
175
+ type GroupToolPolicyBySenderConfig = Record<string, GroupToolPolicyConfig>;
176
+
177
+ /** Config for a single occum.net channel (group) or the wildcard "*" default. */
178
+ interface OccumGroupConfig {
179
+ tools?: GroupToolPolicyConfig;
180
+ toolsBySender?: GroupToolPolicyBySenderConfig;
181
+ }
182
+
183
+ /** ChannelGroupContext passed by OpenClaw to resolveToolPolicy. */
184
+ interface ChannelGroupContext {
185
+ cfg: any;
186
+ groupId?: string | null;
187
+ groupChannel?: string | null;
188
+ groupSpace?: string | null;
189
+ accountId?: string | null;
190
+ senderId?: string | null;
191
+ senderName?: string | null;
192
+ senderUsername?: string | null;
193
+ senderE164?: string | null;
194
+ }
195
+
196
+ const SENDER_KEY_TYPES = ["id", "e164", "username", "name"] as const;
197
+
198
+ /**
199
+ * Match a sender against toolsBySender entries.
200
+ *
201
+ * Resolution order: id → e164 → username → name → wildcard "*".
202
+ * First match wins. All comparisons are case-insensitive.
203
+ */
204
+ function resolveToolsBySender(
205
+ toolsBySender: GroupToolPolicyBySenderConfig | undefined,
206
+ sender: { senderId?: string | null; senderName?: string | null; senderUsername?: string | null; senderE164?: string | null },
207
+ ): GroupToolPolicyConfig | undefined {
208
+ if (!toolsBySender) return undefined;
209
+
210
+ // Build lookup buckets and extract wildcard
211
+ const buckets: Record<string, Map<string, GroupToolPolicyConfig>> = {
212
+ id: new Map(),
213
+ e164: new Map(),
214
+ username: new Map(),
215
+ name: new Map(),
216
+ };
217
+ let wildcard: GroupToolPolicyConfig | undefined;
218
+
219
+ for (const [rawKey, policy] of Object.entries(toolsBySender)) {
220
+ if (!policy) continue;
221
+ const trimmed = rawKey.trim();
222
+ if (!trimmed) continue;
223
+
224
+ if (trimmed === "*") {
225
+ wildcard = policy;
226
+ continue;
227
+ }
228
+
229
+ const lower = trimmed.toLowerCase();
230
+ for (const type of SENDER_KEY_TYPES) {
231
+ const prefix = `${type}:`;
232
+ if (lower.startsWith(prefix)) {
233
+ const value = trimmed.slice(prefix.length).trim().toLowerCase();
234
+ if (value && !buckets[type].has(value)) {
235
+ buckets[type].set(value, policy);
236
+ }
237
+ break;
238
+ }
239
+ }
240
+ }
241
+
242
+ // Match in priority order: id → e164 → username → name → wildcard
243
+ if (sender.senderId) {
244
+ const match = buckets.id.get(sender.senderId.trim().toLowerCase());
245
+ if (match) return match;
246
+ }
247
+ if (sender.senderE164) {
248
+ const match = buckets.e164.get(sender.senderE164.trim().toLowerCase());
249
+ if (match) return match;
250
+ }
251
+ if (sender.senderUsername) {
252
+ const match = buckets.username.get(sender.senderUsername.trim().toLowerCase());
253
+ if (match) return match;
254
+ }
255
+ if (sender.senderName) {
256
+ const match = buckets.name.get(sender.senderName.trim().toLowerCase());
257
+ if (match) return match;
258
+ }
259
+
260
+ return wildcard;
261
+ }
262
+
263
+ /**
264
+ * Resolve the effective tool policy for an inbound occum.net message.
265
+ *
266
+ * Looks up groups config from (in order):
267
+ * 1. channels.occum.accounts.<accountId>.groups (channel-style, if OpenClaw validates it)
268
+ * 2. plugins.entries.occumclaw.config.groups (plugin-style, always safe)
269
+ *
270
+ * Then resolves per-channel and per-sender overrides with wildcard fallback.
271
+ */
272
+ function resolveOccumToolPolicy(params: ChannelGroupContext): GroupToolPolicyConfig | undefined {
273
+ const { cfg, groupId, accountId, senderId, senderName, senderUsername, senderE164 } = params;
274
+
275
+ // Try channel-style config first, then fall back to plugin config
276
+ const acctId = accountId ?? "default";
277
+ const groups: Record<string, OccumGroupConfig> | undefined =
278
+ cfg?.channels?.occum?.accounts?.[acctId]?.groups
279
+ ?? cfg?.plugins?.entries?.occumclaw?.config?.groups;
280
+
281
+ if (!groups || typeof groups !== "object") return undefined;
282
+
283
+ const groupConfig = groupId ? groups[groupId] ?? undefined : undefined;
284
+ const defaultConfig = groups["*"] ?? undefined;
285
+
286
+ const senderInfo = { senderId, senderName, senderUsername, senderE164 };
287
+
288
+ // Priority 1: Group-specific sender override
289
+ const groupSender = resolveToolsBySender(groupConfig?.toolsBySender, senderInfo);
290
+ if (groupSender) return groupSender;
291
+
292
+ // Priority 2: Group-specific tool policy
293
+ if (groupConfig?.tools) return groupConfig.tools;
294
+
295
+ // Priority 3: Wildcard sender override
296
+ const defaultSender = resolveToolsBySender(defaultConfig?.toolsBySender, senderInfo);
297
+ if (defaultSender) return defaultSender;
298
+
299
+ // Priority 4: Wildcard tool policy
300
+ if (defaultConfig?.tools) return defaultConfig.tools;
301
+
302
+ return undefined;
303
+ }
304
+
145
305
  // ─── Account Config ─────────────────────────────────────────────────────────
146
306
 
147
307
  interface OccumAccount {
@@ -212,6 +372,10 @@ function createOccumChannelPlugin(logger: PluginLogger) {
212
372
  chatTypes: ["direct" as const],
213
373
  },
214
374
 
375
+ groups: {
376
+ resolveToolPolicy: resolveOccumToolPolicy,
377
+ },
378
+
215
379
  config: {
216
380
  listAccountIds: (cfg: any) => listAccountIds(cfg),
217
381
  resolveAccount: (cfg: any, accountId?: string | null) => resolveAccount(cfg, accountId),
@@ -369,6 +533,11 @@ function createOccumChannelPlugin(logger: PluginLogger) {
369
533
  const replyChannelId = event.channelId;
370
534
  const sessionId = crypto.randomUUID();
371
535
 
536
+ // Extract sender username from event body if available
537
+ const senderUsername = (event.body as any)?.senderUsername
538
+ ?? (event.body as any)?.username
539
+ ?? undefined;
540
+
372
541
  const msgCtx: MsgContext = {
373
542
  Body: text,
374
543
  BodyForAgent: text,
@@ -381,10 +550,12 @@ function createOccumChannelPlugin(logger: PluginLogger) {
381
550
  AccountId: ctx.accountId,
382
551
  Provider: "occum",
383
552
  ChatType: "direct",
553
+ GroupId: event.channelId,
384
554
  SenderId: event.senderType === "user"
385
555
  ? String(event.senderUserId)
386
556
  : String(event.senderAgentId),
387
557
  SenderName: senderLabel,
558
+ SenderUsername: senderUsername,
388
559
  Timestamp: new Date(event.createdAt).getTime(),
389
560
  CommandAuthorized: true,
390
561
  MessageSid: event.id,
@@ -9,7 +9,39 @@
9
9
  "additionalProperties": false,
10
10
  "properties": {
11
11
  "agentToken": { "type": "string" },
12
- "apiUrl": { "type": "string", "default": "https://api.occum.net" }
12
+ "apiUrl": { "type": "string", "default": "https://api.occum.net" },
13
+ "groups": {
14
+ "type": "object",
15
+ "description": "Per-channel and per-sender tool policy overrides. Keys are channel IDs or \"*\" for wildcard.",
16
+ "additionalProperties": {
17
+ "type": "object",
18
+ "properties": {
19
+ "tools": {
20
+ "type": "object",
21
+ "properties": {
22
+ "allow": { "type": "array", "items": { "type": "string" } },
23
+ "alsoAllow": { "type": "array", "items": { "type": "string" } },
24
+ "deny": { "type": "array", "items": { "type": "string" } }
25
+ },
26
+ "additionalProperties": false
27
+ },
28
+ "toolsBySender": {
29
+ "type": "object",
30
+ "description": "Per-sender overrides keyed by id:<val>, username:<val>, name:<val>, or *",
31
+ "additionalProperties": {
32
+ "type": "object",
33
+ "properties": {
34
+ "allow": { "type": "array", "items": { "type": "string" } },
35
+ "alsoAllow": { "type": "array", "items": { "type": "string" } },
36
+ "deny": { "type": "array", "items": { "type": "string" } }
37
+ },
38
+ "additionalProperties": false
39
+ }
40
+ }
41
+ },
42
+ "additionalProperties": false
43
+ }
44
+ }
13
45
  },
14
46
  "required": []
15
47
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@occum-net/occumclaw",
3
- "version": "0.2.2",
3
+ "version": "0.4.1",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },