@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.
Files changed (2) hide show
  1. package/index.ts +73 -47
  2. 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
- * 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;
@@ -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 (either works; plugin-style avoids config validation issues):
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 (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)
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?.channels?.occum?.accounts?.[acctId]?.groups
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
- * Resolve account config from OpenClaw config.
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` is the occum.net channel ID (UUIDv7 string)
399
- 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;
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
- To: replyChannelId,
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
- GroupId: event.channelId,
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] Reply sent to #${event.channelId} (${payload.text.length} chars)`);
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@occum-net/occumclaw",
3
- "version": "0.4.1",
3
+ "version": "0.5.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },