@occum-net/occumclaw 0.4.0 → 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 CHANGED
@@ -8,12 +8,8 @@
8
8
  * occum.net.
9
9
  *
10
10
  * Configuration (set via `openclaw config set`):
11
- * channels.occum.accounts.default.agentToken — Agent bearer token (occ_...)
12
- * channels.occum.accounts.default.apiUrl — API base URL (default: https://api.occum.net)
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
- To?: string;
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
- GroupId?: string;
75
+ GroupChannel?: string;
78
76
  SenderId?: string;
79
77
  SenderName?: string;
80
78
  SenderUsername?: string;
@@ -155,8 +153,7 @@ interface OpenClawPluginApi {
155
153
  // 5. undefined → fall back to global (tools.allow / tools.profile)
156
154
  //
157
155
  // Config path:
158
- // channels.occum.accounts.<accountId>.groups.<channelId>.tools
159
- // channels.occum.accounts.<accountId>.groups.<channelId>.toolsBySender
156
+ // plugins.entries.occumclaw.config.groups.<channelId>.tools
160
157
  //
161
158
  // Per-sender key format:
162
159
  // "id:<senderId>" — match by user/agent ID
@@ -263,15 +260,16 @@ function resolveToolsBySender(
263
260
  /**
264
261
  * Resolve the effective tool policy for an inbound occum.net message.
265
262
  *
266
- * Looks up config at: channels.occum.accounts.<accountId>.groups.<groupId>
267
- * with fallback to wildcard "*" group, then per-sender overrides.
263
+ * Looks up groups config from:
264
+ * plugins.entries.occumclaw.config.groups
265
+ *
266
+ * Then resolves per-channel and per-sender overrides with wildcard fallback.
268
267
  */
269
268
  function resolveOccumToolPolicy(params: ChannelGroupContext): GroupToolPolicyConfig | undefined {
270
269
  const { cfg, groupId, accountId, senderId, senderName, senderUsername, senderE164 } = params;
271
270
 
272
- const acctId = accountId ?? "default";
273
271
  const groups: Record<string, OccumGroupConfig> | undefined =
274
- cfg?.channels?.occum?.accounts?.[acctId]?.groups;
272
+ cfg?.plugins?.entries?.occumclaw?.config?.groups;
275
273
 
276
274
  if (!groups || typeof groups !== "object") return undefined;
277
275
 
@@ -308,22 +306,8 @@ function resolveApiUrl(raw?: string): string {
308
306
  return (raw ?? "https://api.occum.net").replace(/\/+$/, "") + "/v1";
309
307
  }
310
308
 
311
- /**
312
- * Resolve account config from OpenClaw config.
313
- * Supports both channel-style config and legacy plugin config.
314
- */
315
- function resolveAccount(cfg: any, accountId?: string | null): OccumAccount {
316
- // Channel-style: channels.occum.accounts.<accountId>
317
- const acctId = accountId ?? "default";
318
- const channelAcct = cfg?.channels?.occum?.accounts?.[acctId];
319
- if (channelAcct?.agentToken) {
320
- return {
321
- agentToken: channelAcct.agentToken,
322
- apiUrl: resolveApiUrl(channelAcct.apiUrl),
323
- };
324
- }
325
-
326
- // Plugin config: plugins.entries.occumclaw.config
309
+ /** Resolve account config from plugins.entries.occumclaw.config. */
310
+ function resolveAccount(cfg: any, _accountId?: string | null): OccumAccount {
327
311
  const pluginCfg = cfg?.plugins?.entries?.occumclaw?.config;
328
312
  if (pluginCfg?.agentToken) {
329
313
  return {
@@ -336,12 +320,6 @@ function resolveAccount(cfg: any, accountId?: string | null): OccumAccount {
336
320
  }
337
321
 
338
322
  function listAccountIds(cfg: any): string[] {
339
- // Channel-style accounts
340
- const channelAccounts = cfg?.channels?.occum?.accounts;
341
- if (channelAccounts && typeof channelAccounts === "object") {
342
- return Object.keys(channelAccounts);
343
- }
344
- // Plugin config: single implicit account
345
323
  const pluginCfg = cfg?.plugins?.entries?.occumclaw?.config;
346
324
  if (pluginCfg?.agentToken) {
347
325
  return ["default"];
@@ -352,6 +330,10 @@ function listAccountIds(cfg: any): string[] {
352
330
  // ─── Channel Plugin ─────────────────────────────────────────────────────────
353
331
 
354
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
+
355
337
  return {
356
338
  id: "occum" as const,
357
339
 
@@ -381,6 +363,22 @@ function createOccumChannelPlugin(logger: PluginLogger) {
381
363
  outbound: {
382
364
  deliveryMode: "direct" as const,
383
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
+
384
382
  async sendText(ctx: {
385
383
  cfg: any;
386
384
  to: string;
@@ -390,8 +388,9 @@ function createOccumChannelPlugin(logger: PluginLogger) {
390
388
  const account = resolveAccount(ctx.cfg, ctx.accountId);
391
389
  const config: ToolConfig = { agentToken: account.agentToken, apiUrl: account.apiUrl };
392
390
 
393
- // `to` is the occum.net channel ID (UUIDv7 string)
394
- const channelId = ctx.to;
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;
395
394
 
396
395
  const event = await apiRequest(config, `/channels/${channelId}/messages`, {
397
396
  method: "POST",
@@ -426,11 +425,10 @@ function createOccumChannelPlugin(logger: PluginLogger) {
426
425
 
427
426
  // Discover agent identity
428
427
  let agentId: string | null = null;
429
- let controlChannelId: string | null = null;
430
428
  try {
431
429
  const me = await apiRequest(config, "/agents/me");
432
430
  agentId = me.id;
433
- controlChannelId = me.controlChannelId;
431
+ controlChannelId = me.controlChannelId ?? null;
434
432
  log?.info?.(`Agent identity: ${me.name} (${agentId}), control channel #${controlChannelId}`);
435
433
  } catch (err) {
436
434
  log?.warn?.(`Failed to discover agent identity: ${err}`);
@@ -540,12 +538,14 @@ function createOccumChannelPlugin(logger: PluginLogger) {
540
538
  From: event.senderType === "user"
541
539
  ? `user:${event.senderUserId}`
542
540
  : `agent:${event.senderAgentId}`,
543
- To: replyChannelId,
541
+ OriginatingChannel: "occum",
542
+ OriginatingTo: replyChannelId,
543
+ Surface: "occum",
544
544
  SessionKey: `occum-${ctx.accountId}-${event.channelId}`,
545
545
  AccountId: ctx.accountId,
546
546
  Provider: "occum",
547
547
  ChatType: "direct",
548
- GroupId: event.channelId,
548
+ GroupChannel: event.channelId,
549
549
  SenderId: event.senderType === "user"
550
550
  ? String(event.senderUserId)
551
551
  : String(event.senderAgentId),
@@ -577,15 +577,46 @@ function createOccumChannelPlugin(logger: PluginLogger) {
577
577
  ctx: msgCtx,
578
578
  cfg,
579
579
  dispatcherOptions: {
580
- deliver: async (payload: ReplyPayload) => {
580
+ deliver: async (payload: ReplyPayload, info: ReplyDispatchKindInfo) => {
581
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
582
613
  replyText += payload.text;
583
614
  try {
584
615
  await apiRequest(config, `/channels/${event.channelId}/messages`, {
585
616
  method: "POST",
586
617
  body: JSON.stringify({ text: payload.text }),
587
618
  });
588
- log?.info?.(`[occum] Reply sent to #${event.channelId} (${payload.text.length} chars)`);
619
+ log?.info?.(`[occum] Final reply sent to #${event.channelId} (${payload.text.length} chars)`);
589
620
  } catch (err) {
590
621
  log?.error?.(`[occum] Failed to send reply: ${err}`);
591
622
  }
@@ -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.4.0",
3
+ "version": "0.5.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },