@occum-net/occumclaw 0.4.1 → 0.5.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 +73 -47
- package/package.json +1 -1
package/index.ts
CHANGED
|
@@ -8,12 +8,8 @@
|
|
|
8
8
|
* occum.net.
|
|
9
9
|
*
|
|
10
10
|
* Configuration (set via `openclaw config set`):
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* Legacy config path (auto-migrated):
|
|
15
|
-
* plugins.entries.occumclaw.config.agentToken
|
|
16
|
-
* plugins.entries.occumclaw.config.apiUrl
|
|
11
|
+
* plugins.entries.occumclaw.config.agentToken — Agent bearer token (occ_...)
|
|
12
|
+
* plugins.entries.occumclaw.config.apiUrl — API base URL (default: https://api.occum.net)
|
|
17
13
|
*/
|
|
18
14
|
|
|
19
15
|
import { OccumWsClient, type OccumEvent, type AuthOkData } from "./ws-client";
|
|
@@ -69,12 +65,14 @@ interface MsgContext {
|
|
|
69
65
|
BodyForAgent?: string;
|
|
70
66
|
CommandBody?: string;
|
|
71
67
|
From?: string;
|
|
72
|
-
|
|
68
|
+
OriginatingChannel?: string;
|
|
69
|
+
OriginatingTo?: string;
|
|
70
|
+
Surface?: string;
|
|
73
71
|
SessionKey?: string;
|
|
74
72
|
AccountId?: string;
|
|
75
73
|
Provider?: string;
|
|
76
74
|
ChatType?: string;
|
|
77
|
-
|
|
75
|
+
GroupChannel?: string;
|
|
78
76
|
SenderId?: string;
|
|
79
77
|
SenderName?: string;
|
|
80
78
|
SenderUsername?: string;
|
|
@@ -154,8 +152,7 @@ interface OpenClawPluginApi {
|
|
|
154
152
|
// 4. Wildcard tool policy (groups.*.tools)
|
|
155
153
|
// 5. undefined → fall back to global (tools.allow / tools.profile)
|
|
156
154
|
//
|
|
157
|
-
// Config path
|
|
158
|
-
// channels.occum.accounts.<accountId>.groups.<channelId>.tools
|
|
155
|
+
// Config path:
|
|
159
156
|
// plugins.entries.occumclaw.config.groups.<channelId>.tools
|
|
160
157
|
//
|
|
161
158
|
// Per-sender key format:
|
|
@@ -263,20 +260,16 @@ function resolveToolsBySender(
|
|
|
263
260
|
/**
|
|
264
261
|
* Resolve the effective tool policy for an inbound occum.net message.
|
|
265
262
|
*
|
|
266
|
-
* Looks up groups config from
|
|
267
|
-
*
|
|
268
|
-
* 2. plugins.entries.occumclaw.config.groups (plugin-style, always safe)
|
|
263
|
+
* Looks up groups config from:
|
|
264
|
+
* plugins.entries.occumclaw.config.groups
|
|
269
265
|
*
|
|
270
266
|
* Then resolves per-channel and per-sender overrides with wildcard fallback.
|
|
271
267
|
*/
|
|
272
268
|
function resolveOccumToolPolicy(params: ChannelGroupContext): GroupToolPolicyConfig | undefined {
|
|
273
269
|
const { cfg, groupId, accountId, senderId, senderName, senderUsername, senderE164 } = params;
|
|
274
270
|
|
|
275
|
-
// Try channel-style config first, then fall back to plugin config
|
|
276
|
-
const acctId = accountId ?? "default";
|
|
277
271
|
const groups: Record<string, OccumGroupConfig> | undefined =
|
|
278
|
-
cfg?.
|
|
279
|
-
?? cfg?.plugins?.entries?.occumclaw?.config?.groups;
|
|
272
|
+
cfg?.plugins?.entries?.occumclaw?.config?.groups;
|
|
280
273
|
|
|
281
274
|
if (!groups || typeof groups !== "object") return undefined;
|
|
282
275
|
|
|
@@ -313,22 +306,8 @@ function resolveApiUrl(raw?: string): string {
|
|
|
313
306
|
return (raw ?? "https://api.occum.net").replace(/\/+$/, "") + "/v1";
|
|
314
307
|
}
|
|
315
308
|
|
|
316
|
-
/**
|
|
317
|
-
|
|
318
|
-
* Supports both channel-style config and legacy plugin config.
|
|
319
|
-
*/
|
|
320
|
-
function resolveAccount(cfg: any, accountId?: string | null): OccumAccount {
|
|
321
|
-
// Channel-style: channels.occum.accounts.<accountId>
|
|
322
|
-
const acctId = accountId ?? "default";
|
|
323
|
-
const channelAcct = cfg?.channels?.occum?.accounts?.[acctId];
|
|
324
|
-
if (channelAcct?.agentToken) {
|
|
325
|
-
return {
|
|
326
|
-
agentToken: channelAcct.agentToken,
|
|
327
|
-
apiUrl: resolveApiUrl(channelAcct.apiUrl),
|
|
328
|
-
};
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
// Plugin config: plugins.entries.occumclaw.config
|
|
309
|
+
/** Resolve account config from plugins.entries.occumclaw.config. */
|
|
310
|
+
function resolveAccount(cfg: any, _accountId?: string | null): OccumAccount {
|
|
332
311
|
const pluginCfg = cfg?.plugins?.entries?.occumclaw?.config;
|
|
333
312
|
if (pluginCfg?.agentToken) {
|
|
334
313
|
return {
|
|
@@ -341,12 +320,6 @@ function resolveAccount(cfg: any, accountId?: string | null): OccumAccount {
|
|
|
341
320
|
}
|
|
342
321
|
|
|
343
322
|
function listAccountIds(cfg: any): string[] {
|
|
344
|
-
// Channel-style accounts
|
|
345
|
-
const channelAccounts = cfg?.channels?.occum?.accounts;
|
|
346
|
-
if (channelAccounts && typeof channelAccounts === "object") {
|
|
347
|
-
return Object.keys(channelAccounts);
|
|
348
|
-
}
|
|
349
|
-
// Plugin config: single implicit account
|
|
350
323
|
const pluginCfg = cfg?.plugins?.entries?.occumclaw?.config;
|
|
351
324
|
if (pluginCfg?.agentToken) {
|
|
352
325
|
return ["default"];
|
|
@@ -357,6 +330,10 @@ function listAccountIds(cfg: any): string[] {
|
|
|
357
330
|
// ─── Channel Plugin ─────────────────────────────────────────────────────────
|
|
358
331
|
|
|
359
332
|
function createOccumChannelPlugin(logger: PluginLogger) {
|
|
333
|
+
// Shared state: set by startAccount, read by resolveTarget/sendText.
|
|
334
|
+
// All outbound delivery routes to the agent's control channel.
|
|
335
|
+
let controlChannelId: string | null = null;
|
|
336
|
+
|
|
360
337
|
return {
|
|
361
338
|
id: "occum" as const,
|
|
362
339
|
|
|
@@ -386,6 +363,22 @@ function createOccumChannelPlugin(logger: PluginLogger) {
|
|
|
386
363
|
outbound: {
|
|
387
364
|
deliveryMode: "direct" as const,
|
|
388
365
|
|
|
366
|
+
resolveTarget(params: {
|
|
367
|
+
cfg?: any;
|
|
368
|
+
to?: string;
|
|
369
|
+
allowFrom?: string[];
|
|
370
|
+
accountId?: string | null;
|
|
371
|
+
mode?: string;
|
|
372
|
+
}): { ok: true; to: string } | { ok: false; error: Error } {
|
|
373
|
+
// All outbound delivery routes to the control channel.
|
|
374
|
+
// OpenClaw may pass a user ID (e.g. from "channel": "last" cron
|
|
375
|
+
// delivery) — we resolve it to the control channel regardless.
|
|
376
|
+
if (!controlChannelId) {
|
|
377
|
+
return { ok: false, error: new Error("Occum.net agent not connected yet (no control channel)") };
|
|
378
|
+
}
|
|
379
|
+
return { ok: true, to: controlChannelId };
|
|
380
|
+
},
|
|
381
|
+
|
|
389
382
|
async sendText(ctx: {
|
|
390
383
|
cfg: any;
|
|
391
384
|
to: string;
|
|
@@ -395,8 +388,9 @@ function createOccumChannelPlugin(logger: PluginLogger) {
|
|
|
395
388
|
const account = resolveAccount(ctx.cfg, ctx.accountId);
|
|
396
389
|
const config: ToolConfig = { agentToken: account.agentToken, apiUrl: account.apiUrl };
|
|
397
390
|
|
|
398
|
-
// `to`
|
|
399
|
-
|
|
391
|
+
// Use control channel if `to` doesn't look like a channel ID
|
|
392
|
+
// (e.g. OpenClaw passed a user ID from "channel: last" resolution)
|
|
393
|
+
const channelId = controlChannelId ?? ctx.to;
|
|
400
394
|
|
|
401
395
|
const event = await apiRequest(config, `/channels/${channelId}/messages`, {
|
|
402
396
|
method: "POST",
|
|
@@ -431,11 +425,10 @@ function createOccumChannelPlugin(logger: PluginLogger) {
|
|
|
431
425
|
|
|
432
426
|
// Discover agent identity
|
|
433
427
|
let agentId: string | null = null;
|
|
434
|
-
let controlChannelId: string | null = null;
|
|
435
428
|
try {
|
|
436
429
|
const me = await apiRequest(config, "/agents/me");
|
|
437
430
|
agentId = me.id;
|
|
438
|
-
controlChannelId = me.controlChannelId;
|
|
431
|
+
controlChannelId = me.controlChannelId ?? null;
|
|
439
432
|
log?.info?.(`Agent identity: ${me.name} (${agentId}), control channel #${controlChannelId}`);
|
|
440
433
|
} catch (err) {
|
|
441
434
|
log?.warn?.(`Failed to discover agent identity: ${err}`);
|
|
@@ -545,12 +538,14 @@ function createOccumChannelPlugin(logger: PluginLogger) {
|
|
|
545
538
|
From: event.senderType === "user"
|
|
546
539
|
? `user:${event.senderUserId}`
|
|
547
540
|
: `agent:${event.senderAgentId}`,
|
|
548
|
-
|
|
541
|
+
OriginatingChannel: "occum",
|
|
542
|
+
OriginatingTo: replyChannelId,
|
|
543
|
+
Surface: "occum",
|
|
549
544
|
SessionKey: `occum-${ctx.accountId}-${event.channelId}`,
|
|
550
545
|
AccountId: ctx.accountId,
|
|
551
546
|
Provider: "occum",
|
|
552
547
|
ChatType: "direct",
|
|
553
|
-
|
|
548
|
+
GroupChannel: event.channelId,
|
|
554
549
|
SenderId: event.senderType === "user"
|
|
555
550
|
? String(event.senderUserId)
|
|
556
551
|
: String(event.senderAgentId),
|
|
@@ -582,15 +577,46 @@ function createOccumChannelPlugin(logger: PluginLogger) {
|
|
|
582
577
|
ctx: msgCtx,
|
|
583
578
|
cfg,
|
|
584
579
|
dispatcherOptions: {
|
|
585
|
-
deliver: async (payload: ReplyPayload) => {
|
|
580
|
+
deliver: async (payload: ReplyPayload, info: ReplyDispatchKindInfo) => {
|
|
586
581
|
if (!payload.text) return;
|
|
582
|
+
|
|
583
|
+
// Reasoning/thinking blocks → work update
|
|
584
|
+
if (payload.isReasoning) {
|
|
585
|
+
log?.info?.(`[occum] Work update (thinking, ${payload.text.length} chars)`);
|
|
586
|
+
apiRequest(config, `/channels/${event.channelId}/work/${sessionId}/update`, {
|
|
587
|
+
method: "POST",
|
|
588
|
+
body: JSON.stringify({ updateType: "thinking", content: payload.text }),
|
|
589
|
+
}).catch((err) => log?.warn?.(`[occum] Failed to post thinking update: ${err}`));
|
|
590
|
+
return;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
// Tool results and intermediate blocks → work updates
|
|
594
|
+
if (info.kind === "tool") {
|
|
595
|
+
log?.info?.(`[occum] Work update (tool, ${payload.text.length} chars)`);
|
|
596
|
+
apiRequest(config, `/channels/${event.channelId}/work/${sessionId}/update`, {
|
|
597
|
+
method: "POST",
|
|
598
|
+
body: JSON.stringify({ updateType: "tool_use", content: payload.text }),
|
|
599
|
+
}).catch((err) => log?.warn?.(`[occum] Failed to post tool update: ${err}`));
|
|
600
|
+
return;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
if (info.kind === "block") {
|
|
604
|
+
log?.info?.(`[occum] Work update (text, ${payload.text.length} chars)`);
|
|
605
|
+
apiRequest(config, `/channels/${event.channelId}/work/${sessionId}/update`, {
|
|
606
|
+
method: "POST",
|
|
607
|
+
body: JSON.stringify({ updateType: "text", content: payload.text }),
|
|
608
|
+
}).catch((err) => log?.warn?.(`[occum] Failed to post text update: ${err}`));
|
|
609
|
+
return;
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
// Final reply → send as actual channel message
|
|
587
613
|
replyText += payload.text;
|
|
588
614
|
try {
|
|
589
615
|
await apiRequest(config, `/channels/${event.channelId}/messages`, {
|
|
590
616
|
method: "POST",
|
|
591
617
|
body: JSON.stringify({ text: payload.text }),
|
|
592
618
|
});
|
|
593
|
-
log?.info?.(`[occum]
|
|
619
|
+
log?.info?.(`[occum] Final reply sent to #${event.channelId} (${payload.text.length} chars)`);
|
|
594
620
|
} catch (err) {
|
|
595
621
|
log?.error?.(`[occum] Failed to send reply: ${err}`);
|
|
596
622
|
}
|