@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.
- package/index.ts +185 -10
- 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
|
|
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
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
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,
|