@occum-net/occumclaw 0.4.1 → 0.6.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 +273 -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"];
|
|
@@ -354,9 +327,86 @@ function listAccountIds(cfg: any): string[] {
|
|
|
354
327
|
return [];
|
|
355
328
|
}
|
|
356
329
|
|
|
330
|
+
// ─── Directory Helpers ───────────────────────────────────────────────────────
|
|
331
|
+
|
|
332
|
+
interface DirectoryEntry {
|
|
333
|
+
kind: "user" | "group" | "channel";
|
|
334
|
+
id: string;
|
|
335
|
+
name?: string;
|
|
336
|
+
handle?: string;
|
|
337
|
+
avatarUrl?: string;
|
|
338
|
+
rank?: number;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/** Case-insensitive substring filter on id, name, and handle. */
|
|
342
|
+
function filterEntries(entries: DirectoryEntry[], query?: string | null, limit?: number | null): DirectoryEntry[] {
|
|
343
|
+
if (query) {
|
|
344
|
+
const q = query.trim().toLowerCase();
|
|
345
|
+
entries = entries.filter(
|
|
346
|
+
(e) =>
|
|
347
|
+
e.id.toLowerCase().includes(q) ||
|
|
348
|
+
e.name?.toLowerCase().includes(q) ||
|
|
349
|
+
e.handle?.toLowerCase().includes(q),
|
|
350
|
+
);
|
|
351
|
+
}
|
|
352
|
+
if (limit && limit > 0) entries = entries.slice(0, limit);
|
|
353
|
+
return entries;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/** Convert an occum.net channel member to a directory entry. */
|
|
357
|
+
function memberToEntry(m: any): DirectoryEntry {
|
|
358
|
+
return {
|
|
359
|
+
kind: "user",
|
|
360
|
+
id: m.memberType === "agent" ? m.agentId : m.userId,
|
|
361
|
+
name: m.displayName ?? m.invitedName ?? undefined,
|
|
362
|
+
handle: m.memberType === "agent" ? m.agentId : undefined,
|
|
363
|
+
avatarUrl: m.avatarUrl ?? undefined,
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/** Convert an occum.net channel to a directory group entry. */
|
|
368
|
+
function channelToEntry(ch: any): DirectoryEntry {
|
|
369
|
+
return {
|
|
370
|
+
kind: "group",
|
|
371
|
+
id: ch.id,
|
|
372
|
+
name: ch.name ?? undefined,
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Build a ToolConfig from either the gateway closure or from the cfg param.
|
|
378
|
+
* The gateway sets cachedConfig/controlChannelId at startup; the CLI resolver
|
|
379
|
+
* only has cfg, so we fall back to resolveAccount + /agents/me.
|
|
380
|
+
*/
|
|
381
|
+
async function resolveDirectoryContext(
|
|
382
|
+
cachedConfig: ToolConfig | null,
|
|
383
|
+
controlChannelId: string | null,
|
|
384
|
+
cfg?: any,
|
|
385
|
+
): Promise<{ config: ToolConfig; controlChannelId: string } | null> {
|
|
386
|
+
if (cachedConfig && controlChannelId) {
|
|
387
|
+
return { config: cachedConfig, controlChannelId };
|
|
388
|
+
}
|
|
389
|
+
// Fallback: build config from cfg param (used by CLI commands)
|
|
390
|
+
const account = resolveAccount(cfg ?? {});
|
|
391
|
+
if (!account.agentToken) return null;
|
|
392
|
+
const config: ToolConfig = { agentToken: account.agentToken, apiUrl: account.apiUrl };
|
|
393
|
+
try {
|
|
394
|
+
const me = await apiRequest(config, "/agents/me");
|
|
395
|
+
if (!me.controlChannelId) return null;
|
|
396
|
+
return { config, controlChannelId: me.controlChannelId };
|
|
397
|
+
} catch {
|
|
398
|
+
return null;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
357
402
|
// ─── Channel Plugin ─────────────────────────────────────────────────────────
|
|
358
403
|
|
|
359
404
|
function createOccumChannelPlugin(logger: PluginLogger) {
|
|
405
|
+
// Shared state: set by startAccount, read by resolveTarget/sendText/directory.
|
|
406
|
+
// All outbound delivery routes to the agent's control channel.
|
|
407
|
+
let controlChannelId: string | null = null;
|
|
408
|
+
let cachedConfig: ToolConfig | null = null;
|
|
409
|
+
|
|
360
410
|
return {
|
|
361
411
|
id: "occum" as const,
|
|
362
412
|
|
|
@@ -376,6 +426,132 @@ function createOccumChannelPlugin(logger: PluginLogger) {
|
|
|
376
426
|
resolveToolPolicy: resolveOccumToolPolicy,
|
|
377
427
|
},
|
|
378
428
|
|
|
429
|
+
directory: {
|
|
430
|
+
async self(params: { cfg?: any }) {
|
|
431
|
+
const ctx = await resolveDirectoryContext(cachedConfig, controlChannelId, params.cfg);
|
|
432
|
+
if (!ctx) return null;
|
|
433
|
+
try {
|
|
434
|
+
const me = await apiRequest(ctx.config, "/agents/me");
|
|
435
|
+
return { kind: "user" as const, id: me.id, name: me.name ?? undefined };
|
|
436
|
+
} catch {
|
|
437
|
+
return null;
|
|
438
|
+
}
|
|
439
|
+
},
|
|
440
|
+
|
|
441
|
+
async listPeers(params: { cfg?: any; query?: string | null; limit?: number | null; accountId?: string | null }) {
|
|
442
|
+
const ctx = await resolveDirectoryContext(cachedConfig, controlChannelId, params.cfg);
|
|
443
|
+
if (!ctx) return [];
|
|
444
|
+
try {
|
|
445
|
+
const members: any[] = await apiRequest(ctx.config, `/channels/${ctx.controlChannelId}/members`);
|
|
446
|
+
const entries = members.map(memberToEntry).filter((e) => !!e.id);
|
|
447
|
+
return filterEntries(entries, params.query, params.limit);
|
|
448
|
+
} catch {
|
|
449
|
+
return [];
|
|
450
|
+
}
|
|
451
|
+
},
|
|
452
|
+
|
|
453
|
+
async listPeersLive(params: { cfg?: any; query?: string | null; limit?: number | null; accountId?: string | null }) {
|
|
454
|
+
const ctx = await resolveDirectoryContext(cachedConfig, controlChannelId, params.cfg);
|
|
455
|
+
if (!ctx) return [];
|
|
456
|
+
try {
|
|
457
|
+
const members: any[] = await apiRequest(ctx.config, `/channels/${ctx.controlChannelId}/members`);
|
|
458
|
+
const entries = members.map(memberToEntry).filter((e) => !!e.id);
|
|
459
|
+
return filterEntries(entries, params.query, params.limit);
|
|
460
|
+
} catch {
|
|
461
|
+
return [];
|
|
462
|
+
}
|
|
463
|
+
},
|
|
464
|
+
|
|
465
|
+
async listGroups(params: { cfg?: any; query?: string | null; limit?: number | null; accountId?: string | null }) {
|
|
466
|
+
const ctx = await resolveDirectoryContext(cachedConfig, controlChannelId, params.cfg);
|
|
467
|
+
if (!ctx) return [];
|
|
468
|
+
try {
|
|
469
|
+
const channels: any[] = await apiRequest(ctx.config, "/channels");
|
|
470
|
+
const entries = channels.map(channelToEntry);
|
|
471
|
+
return filterEntries(entries, params.query, params.limit);
|
|
472
|
+
} catch {
|
|
473
|
+
return [];
|
|
474
|
+
}
|
|
475
|
+
},
|
|
476
|
+
|
|
477
|
+
async listGroupsLive(params: { cfg?: any; query?: string | null; limit?: number | null; accountId?: string | null }) {
|
|
478
|
+
const ctx = await resolveDirectoryContext(cachedConfig, controlChannelId, params.cfg);
|
|
479
|
+
if (!ctx) return [];
|
|
480
|
+
try {
|
|
481
|
+
const channels: any[] = await apiRequest(ctx.config, "/channels");
|
|
482
|
+
const entries = channels.map(channelToEntry);
|
|
483
|
+
return filterEntries(entries, params.query, params.limit);
|
|
484
|
+
} catch {
|
|
485
|
+
return [];
|
|
486
|
+
}
|
|
487
|
+
},
|
|
488
|
+
|
|
489
|
+
async listGroupMembers(params: { cfg?: any; groupId: string; limit?: number | null; accountId?: string | null }) {
|
|
490
|
+
const ctx = await resolveDirectoryContext(cachedConfig, controlChannelId, params.cfg);
|
|
491
|
+
if (!ctx) return [];
|
|
492
|
+
try {
|
|
493
|
+
const members: any[] = await apiRequest(ctx.config, `/channels/${params.groupId}/members`);
|
|
494
|
+
const entries = members.map(memberToEntry).filter((e) => !!e.id);
|
|
495
|
+
return params.limit && params.limit > 0 ? entries.slice(0, params.limit) : entries;
|
|
496
|
+
} catch {
|
|
497
|
+
return [];
|
|
498
|
+
}
|
|
499
|
+
},
|
|
500
|
+
},
|
|
501
|
+
|
|
502
|
+
resolver: {
|
|
503
|
+
async resolveTargets(params: {
|
|
504
|
+
cfg?: any;
|
|
505
|
+
accountId?: string | null;
|
|
506
|
+
inputs: string[];
|
|
507
|
+
kind: "user" | "group";
|
|
508
|
+
}) {
|
|
509
|
+
const ctx = await resolveDirectoryContext(cachedConfig, controlChannelId, params.cfg);
|
|
510
|
+
if (!ctx) {
|
|
511
|
+
return params.inputs.map((input) => ({ input, resolved: false, note: "Not configured" }));
|
|
512
|
+
}
|
|
513
|
+
if (params.kind === "group") {
|
|
514
|
+
try {
|
|
515
|
+
const channels: any[] = await apiRequest(ctx.config, "/channels");
|
|
516
|
+
return params.inputs.map((input) => {
|
|
517
|
+
const q = input.trim().toLowerCase();
|
|
518
|
+
const match = channels.find(
|
|
519
|
+
(ch) => ch.id === input || ch.name?.toLowerCase().includes(q),
|
|
520
|
+
);
|
|
521
|
+
return match
|
|
522
|
+
? { input, resolved: true, id: match.id, name: match.name ?? undefined }
|
|
523
|
+
: { input, resolved: false };
|
|
524
|
+
});
|
|
525
|
+
} catch {
|
|
526
|
+
return params.inputs.map((input) => ({ input, resolved: false }));
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
// kind === "user"
|
|
530
|
+
try {
|
|
531
|
+
const members: any[] = await apiRequest(ctx.config, `/channels/${ctx.controlChannelId}/members`);
|
|
532
|
+
return params.inputs.map((input) => {
|
|
533
|
+
const q = input.trim().toLowerCase();
|
|
534
|
+
const match = members.find(
|
|
535
|
+
(m) =>
|
|
536
|
+
m.userId === input ||
|
|
537
|
+
m.agentId === input ||
|
|
538
|
+
m.displayName?.toLowerCase().includes(q) ||
|
|
539
|
+
m.invitedName?.toLowerCase().includes(q),
|
|
540
|
+
);
|
|
541
|
+
if (!match) return { input, resolved: false };
|
|
542
|
+
return {
|
|
543
|
+
input,
|
|
544
|
+
resolved: true,
|
|
545
|
+
id: match.memberType === "agent" ? match.agentId : match.userId,
|
|
546
|
+
name: match.displayName ?? match.invitedName ?? undefined,
|
|
547
|
+
};
|
|
548
|
+
});
|
|
549
|
+
} catch {
|
|
550
|
+
return params.inputs.map((input) => ({ input, resolved: false }));
|
|
551
|
+
}
|
|
552
|
+
},
|
|
553
|
+
},
|
|
554
|
+
|
|
379
555
|
config: {
|
|
380
556
|
listAccountIds: (cfg: any) => listAccountIds(cfg),
|
|
381
557
|
resolveAccount: (cfg: any, accountId?: string | null) => resolveAccount(cfg, accountId),
|
|
@@ -386,6 +562,22 @@ function createOccumChannelPlugin(logger: PluginLogger) {
|
|
|
386
562
|
outbound: {
|
|
387
563
|
deliveryMode: "direct" as const,
|
|
388
564
|
|
|
565
|
+
resolveTarget(params: {
|
|
566
|
+
cfg?: any;
|
|
567
|
+
to?: string;
|
|
568
|
+
allowFrom?: string[];
|
|
569
|
+
accountId?: string | null;
|
|
570
|
+
mode?: string;
|
|
571
|
+
}): { ok: true; to: string } | { ok: false; error: Error } {
|
|
572
|
+
// All outbound delivery routes to the control channel.
|
|
573
|
+
// OpenClaw may pass a user ID (e.g. from "channel": "last" cron
|
|
574
|
+
// delivery) — we resolve it to the control channel regardless.
|
|
575
|
+
if (!controlChannelId) {
|
|
576
|
+
return { ok: false, error: new Error("Occum.net agent not connected yet (no control channel)") };
|
|
577
|
+
}
|
|
578
|
+
return { ok: true, to: controlChannelId };
|
|
579
|
+
},
|
|
580
|
+
|
|
389
581
|
async sendText(ctx: {
|
|
390
582
|
cfg: any;
|
|
391
583
|
to: string;
|
|
@@ -395,8 +587,9 @@ function createOccumChannelPlugin(logger: PluginLogger) {
|
|
|
395
587
|
const account = resolveAccount(ctx.cfg, ctx.accountId);
|
|
396
588
|
const config: ToolConfig = { agentToken: account.agentToken, apiUrl: account.apiUrl };
|
|
397
589
|
|
|
398
|
-
// `to`
|
|
399
|
-
|
|
590
|
+
// Use control channel if `to` doesn't look like a channel ID
|
|
591
|
+
// (e.g. OpenClaw passed a user ID from "channel: last" resolution)
|
|
592
|
+
const channelId = controlChannelId ?? ctx.to;
|
|
400
593
|
|
|
401
594
|
const event = await apiRequest(config, `/channels/${channelId}/messages`, {
|
|
402
595
|
method: "POST",
|
|
@@ -428,14 +621,14 @@ function createOccumChannelPlugin(logger: PluginLogger) {
|
|
|
428
621
|
|
|
429
622
|
const channelRuntime = ctx.channelRuntime;
|
|
430
623
|
const config: ToolConfig = { agentToken: account.agentToken, apiUrl: account.apiUrl };
|
|
624
|
+
cachedConfig = config;
|
|
431
625
|
|
|
432
626
|
// Discover agent identity
|
|
433
627
|
let agentId: string | null = null;
|
|
434
|
-
let controlChannelId: string | null = null;
|
|
435
628
|
try {
|
|
436
629
|
const me = await apiRequest(config, "/agents/me");
|
|
437
630
|
agentId = me.id;
|
|
438
|
-
controlChannelId = me.controlChannelId;
|
|
631
|
+
controlChannelId = me.controlChannelId ?? null;
|
|
439
632
|
log?.info?.(`Agent identity: ${me.name} (${agentId}), control channel #${controlChannelId}`);
|
|
440
633
|
} catch (err) {
|
|
441
634
|
log?.warn?.(`Failed to discover agent identity: ${err}`);
|
|
@@ -545,12 +738,14 @@ function createOccumChannelPlugin(logger: PluginLogger) {
|
|
|
545
738
|
From: event.senderType === "user"
|
|
546
739
|
? `user:${event.senderUserId}`
|
|
547
740
|
: `agent:${event.senderAgentId}`,
|
|
548
|
-
|
|
741
|
+
OriginatingChannel: "occum",
|
|
742
|
+
OriginatingTo: replyChannelId,
|
|
743
|
+
Surface: "occum",
|
|
549
744
|
SessionKey: `occum-${ctx.accountId}-${event.channelId}`,
|
|
550
745
|
AccountId: ctx.accountId,
|
|
551
746
|
Provider: "occum",
|
|
552
747
|
ChatType: "direct",
|
|
553
|
-
|
|
748
|
+
GroupChannel: event.channelId,
|
|
554
749
|
SenderId: event.senderType === "user"
|
|
555
750
|
? String(event.senderUserId)
|
|
556
751
|
: String(event.senderAgentId),
|
|
@@ -582,15 +777,46 @@ function createOccumChannelPlugin(logger: PluginLogger) {
|
|
|
582
777
|
ctx: msgCtx,
|
|
583
778
|
cfg,
|
|
584
779
|
dispatcherOptions: {
|
|
585
|
-
deliver: async (payload: ReplyPayload) => {
|
|
780
|
+
deliver: async (payload: ReplyPayload, info: ReplyDispatchKindInfo) => {
|
|
586
781
|
if (!payload.text) return;
|
|
782
|
+
|
|
783
|
+
// Reasoning/thinking blocks → work update
|
|
784
|
+
if (payload.isReasoning) {
|
|
785
|
+
log?.info?.(`[occum] Work update (thinking, ${payload.text.length} chars)`);
|
|
786
|
+
apiRequest(config, `/channels/${event.channelId}/work/${sessionId}/update`, {
|
|
787
|
+
method: "POST",
|
|
788
|
+
body: JSON.stringify({ updateType: "thinking", content: payload.text }),
|
|
789
|
+
}).catch((err) => log?.warn?.(`[occum] Failed to post thinking update: ${err}`));
|
|
790
|
+
return;
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
// Tool results and intermediate blocks → work updates
|
|
794
|
+
if (info.kind === "tool") {
|
|
795
|
+
log?.info?.(`[occum] Work update (tool, ${payload.text.length} chars)`);
|
|
796
|
+
apiRequest(config, `/channels/${event.channelId}/work/${sessionId}/update`, {
|
|
797
|
+
method: "POST",
|
|
798
|
+
body: JSON.stringify({ updateType: "tool_use", content: payload.text }),
|
|
799
|
+
}).catch((err) => log?.warn?.(`[occum] Failed to post tool update: ${err}`));
|
|
800
|
+
return;
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
if (info.kind === "block") {
|
|
804
|
+
log?.info?.(`[occum] Work update (text, ${payload.text.length} chars)`);
|
|
805
|
+
apiRequest(config, `/channels/${event.channelId}/work/${sessionId}/update`, {
|
|
806
|
+
method: "POST",
|
|
807
|
+
body: JSON.stringify({ updateType: "text", content: payload.text }),
|
|
808
|
+
}).catch((err) => log?.warn?.(`[occum] Failed to post text update: ${err}`));
|
|
809
|
+
return;
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
// Final reply → send as actual channel message
|
|
587
813
|
replyText += payload.text;
|
|
588
814
|
try {
|
|
589
815
|
await apiRequest(config, `/channels/${event.channelId}/messages`, {
|
|
590
816
|
method: "POST",
|
|
591
817
|
body: JSON.stringify({ text: payload.text }),
|
|
592
818
|
});
|
|
593
|
-
log?.info?.(`[occum]
|
|
819
|
+
log?.info?.(`[occum] Final reply sent to #${event.channelId} (${payload.text.length} chars)`);
|
|
594
820
|
} catch (err) {
|
|
595
821
|
log?.error?.(`[occum] Failed to send reply: ${err}`);
|
|
596
822
|
}
|