@occum-net/occumclaw 0.2.1 → 0.4.0

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.
Files changed (2) hide show
  1. package/index.ts +185 -10
  2. package/package.json +1 -1
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,159 @@ 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:
158
+ // channels.occum.accounts.<accountId>.groups.<channelId>.tools
159
+ // channels.occum.accounts.<accountId>.groups.<channelId>.toolsBySender
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 config at: channels.occum.accounts.<accountId>.groups.<groupId>
267
+ * with fallback to wildcard "*" group, then per-sender overrides.
268
+ */
269
+ function resolveOccumToolPolicy(params: ChannelGroupContext): GroupToolPolicyConfig | undefined {
270
+ const { cfg, groupId, accountId, senderId, senderName, senderUsername, senderE164 } = params;
271
+
272
+ const acctId = accountId ?? "default";
273
+ const groups: Record<string, OccumGroupConfig> | undefined =
274
+ cfg?.channels?.occum?.accounts?.[acctId]?.groups;
275
+
276
+ if (!groups || typeof groups !== "object") return undefined;
277
+
278
+ const groupConfig = groupId ? groups[groupId] ?? undefined : undefined;
279
+ const defaultConfig = groups["*"] ?? undefined;
280
+
281
+ const senderInfo = { senderId, senderName, senderUsername, senderE164 };
282
+
283
+ // Priority 1: Group-specific sender override
284
+ const groupSender = resolveToolsBySender(groupConfig?.toolsBySender, senderInfo);
285
+ if (groupSender) return groupSender;
286
+
287
+ // Priority 2: Group-specific tool policy
288
+ if (groupConfig?.tools) return groupConfig.tools;
289
+
290
+ // Priority 3: Wildcard sender override
291
+ const defaultSender = resolveToolsBySender(defaultConfig?.toolsBySender, senderInfo);
292
+ if (defaultSender) return defaultSender;
293
+
294
+ // Priority 4: Wildcard tool policy
295
+ if (defaultConfig?.tools) return defaultConfig.tools;
296
+
297
+ return undefined;
298
+ }
299
+
145
300
  // ─── Account Config ─────────────────────────────────────────────────────────
146
301
 
147
302
  interface OccumAccount {
@@ -212,6 +367,10 @@ function createOccumChannelPlugin(logger: PluginLogger) {
212
367
  chatTypes: ["direct" as const],
213
368
  },
214
369
 
370
+ groups: {
371
+ resolveToolPolicy: resolveOccumToolPolicy,
372
+ },
373
+
215
374
  config: {
216
375
  listAccountIds: (cfg: any) => listAccountIds(cfg),
217
376
  resolveAccount: (cfg: any, accountId?: string | null) => resolveAccount(cfg, accountId),
@@ -288,6 +447,11 @@ function createOccumChannelPlugin(logger: PluginLogger) {
288
447
  // Track processed events to avoid re-dispatch
289
448
  const processedEvents = new Set<string>();
290
449
 
450
+ // Only send the connect greeting once per version (not on every reconnect)
451
+ let greetedVersion: string | null = null;
452
+ // Only notify about a specific update version once
453
+ let notifiedUpdateVersion: string | null = null;
454
+
291
455
  const wsClient = new OccumWsClient({
292
456
  apiUrl: account.apiUrl,
293
457
  agentToken: account.agentToken,
@@ -305,8 +469,9 @@ function createOccumChannelPlugin(logger: PluginLogger) {
305
469
  connected: true,
306
470
  });
307
471
 
308
- // Send a greeting to the control channel on connect
309
- if (controlChannelId) {
472
+ // Send a greeting to the control channel only once per version
473
+ if (controlChannelId && greetedVersion !== PLUGIN_VERSION) {
474
+ greetedVersion = PLUGIN_VERSION;
310
475
  try {
311
476
  await apiRequest(config, `/channels/${controlChannelId}/messages`, {
312
477
  method: "POST",
@@ -318,18 +483,21 @@ function createOccumChannelPlugin(logger: PluginLogger) {
318
483
  }
319
484
  }
320
485
 
321
- // Check for updates in the background
322
- checkLatestVersion().then((latest) => {
323
- if (!latest || latest === PLUGIN_VERSION) return;
324
- const msg = `Update available: v${PLUGIN_VERSION} → v${latest}. Run: openclaw plugins install ${NPM_PACKAGE}`;
325
- log?.warn?.(msg);
326
- if (controlChannelId) {
486
+ // Check for updates in the background on every reconnect,
487
+ // but only notify the user once per discovered version
488
+ if (controlChannelId) {
489
+ checkLatestVersion().then((latest) => {
490
+ if (!latest || latest === PLUGIN_VERSION) return;
491
+ if (latest === notifiedUpdateVersion) return;
492
+ notifiedUpdateVersion = latest;
493
+ const msg = `Update available: v${PLUGIN_VERSION} → v${latest}. Run:\ncurl -fsSL https://occum.net/update-open-claw.sh | bash`;
494
+ log?.warn?.(msg);
327
495
  apiRequest(config, `/channels/${controlChannelId}/messages`, {
328
496
  method: "POST",
329
497
  body: JSON.stringify({ text: msg }),
330
498
  }).catch(() => {});
331
- }
332
- });
499
+ });
500
+ }
333
501
  },
334
502
 
335
503
  onEvent: (event: OccumEvent) => {
@@ -360,6 +528,11 @@ function createOccumChannelPlugin(logger: PluginLogger) {
360
528
  const replyChannelId = event.channelId;
361
529
  const sessionId = crypto.randomUUID();
362
530
 
531
+ // Extract sender username from event body if available
532
+ const senderUsername = (event.body as any)?.senderUsername
533
+ ?? (event.body as any)?.username
534
+ ?? undefined;
535
+
363
536
  const msgCtx: MsgContext = {
364
537
  Body: text,
365
538
  BodyForAgent: text,
@@ -372,10 +545,12 @@ function createOccumChannelPlugin(logger: PluginLogger) {
372
545
  AccountId: ctx.accountId,
373
546
  Provider: "occum",
374
547
  ChatType: "direct",
548
+ GroupId: event.channelId,
375
549
  SenderId: event.senderType === "user"
376
550
  ? String(event.senderUserId)
377
551
  : String(event.senderAgentId),
378
552
  SenderName: senderLabel,
553
+ SenderUsername: senderUsername,
379
554
  Timestamp: new Date(event.createdAt).getTime(),
380
555
  CommandAuthorized: true,
381
556
  MessageSid: event.id,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@occum-net/occumclaw",
3
- "version": "0.2.1",
3
+ "version": "0.4.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },