openclaw-lark-multi-agent 0.1.4 → 0.1.5

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.
@@ -25,8 +25,9 @@ export declare class OpenClawClient {
25
25
  private sessionMessageCallbacks;
26
26
  /** Session keys that should be re-subscribed on reconnect */
27
27
  private subscribedKeys;
28
- /** Session keys with active chatSend — suppress proactive message delivery */
28
+ /** Session keys with active/recent chatSend — suppress proactive message delivery */
29
29
  private suppressedSessions;
30
+ private suppressedSessionTimers;
30
31
  constructor(config: OpenClawConfig);
31
32
  connect(): Promise<void>;
32
33
  private _doConnect;
@@ -68,6 +69,8 @@ export declare class OpenClawClient {
68
69
  * deliver=false prevents OpenClaw from auto-posting to channels.
69
70
  */
70
71
  abortChat(sessionKey: string, runId: string): Promise<any>;
72
+ private suppressSessionKeys;
73
+ private releaseSuppressedSessionKeysAfter;
71
74
  chatSend(params: {
72
75
  sessionKey: string;
73
76
  message: string;
@@ -21,8 +21,9 @@ export class OpenClawClient {
21
21
  sessionMessageCallbacks = new Map();
22
22
  /** Session keys that should be re-subscribed on reconnect */
23
23
  subscribedKeys = new Set();
24
- /** Session keys with active chatSend — suppress proactive message delivery */
24
+ /** Session keys with active/recent chatSend — suppress proactive message delivery */
25
25
  suppressedSessions = new Set();
26
+ suppressedSessionTimers = new Map();
26
27
  constructor(config) {
27
28
  this.config = config;
28
29
  }
@@ -149,11 +150,23 @@ export class OpenClawClient {
149
150
  proactiveText = content;
150
151
  }
151
152
  else if (Array.isArray(content)) {
152
- proactiveText = content
153
- .filter((part) => part?.type === "text" && typeof part.text === "string")
154
- .map((part) => part.text)
155
- .join("\n")
156
- .trim();
153
+ const hasToolBlock = content.some((part) => {
154
+ const type = String(part?.type || "").toLowerCase();
155
+ return type === "toolcall" || type === "tool_call" || type === "tooluse" || type === "tool_use" || type === "toolresult" || type === "tool_result";
156
+ });
157
+ // Do not deliver mixed text+toolCall assistant messages through
158
+ // the proactive final-text path; those are usually intermediate
159
+ // reasoning/status during a tool loop. Tool calls are still
160
+ // delivered via the verbose channel from agent item events when
161
+ // /verbose is enabled. Cron final messages arrive as text-only
162
+ // (optionally with thinking).
163
+ if (!hasToolBlock) {
164
+ proactiveText = content
165
+ .filter((part) => part?.type === "text" && typeof part.text === "string")
166
+ .map((part) => part.text)
167
+ .join("\n")
168
+ .trim();
169
+ }
157
170
  }
158
171
  }
159
172
  if (proactiveText) {
@@ -438,11 +451,32 @@ export class OpenClawClient {
438
451
  const key = sessionKey.startsWith("agent:main:") ? sessionKey.slice("agent:main:".length) : sessionKey;
439
452
  return this.rpc("chat.abort", { sessionKey: key, runId }, 5000).catch(() => { });
440
453
  }
454
+ suppressSessionKeys(keys) {
455
+ for (const key of keys) {
456
+ const timer = this.suppressedSessionTimers.get(key);
457
+ if (timer)
458
+ clearTimeout(timer);
459
+ this.suppressedSessionTimers.delete(key);
460
+ this.suppressedSessions.add(key);
461
+ }
462
+ }
463
+ releaseSuppressedSessionKeysAfter(keys, delayMs) {
464
+ for (const key of keys) {
465
+ const oldTimer = this.suppressedSessionTimers.get(key);
466
+ if (oldTimer)
467
+ clearTimeout(oldTimer);
468
+ const timer = setTimeout(() => {
469
+ this.suppressedSessions.delete(key);
470
+ this.suppressedSessionTimers.delete(key);
471
+ }, delayMs);
472
+ this.suppressedSessionTimers.set(key, timer);
473
+ }
474
+ }
441
475
  async chatSend(params) {
442
476
  const sk = params.sessionKey;
443
477
  const fullSessionKey = `agent:main:${sk}`;
444
- this.suppressedSessions.add(sk);
445
- this.suppressedSessions.add(fullSessionKey);
478
+ const suppressedKeys = [sk, fullSessionKey];
479
+ this.suppressSessionKeys(suppressedKeys);
446
480
  try {
447
481
  // Drop stale buffered events for this session before starting a new run.
448
482
  // This prevents an old final text (e.g. previous "ok") from being consumed by
@@ -461,8 +495,11 @@ export class OpenClawClient {
461
495
  return await this.collectReply(result.runId, params.timeoutMs || 1800000, sk);
462
496
  }
463
497
  finally {
464
- this.suppressedSessions.delete(sk);
465
- this.suppressedSessions.delete(fullSessionKey);
498
+ // OpenClaw can emit the final assistant session.message a moment after
499
+ // collectReply returns. Keep a short grace window so normal chat replies
500
+ // are not delivered twice via the proactive-message path. Cron/LMA runs
501
+ // are unaffected because they do not go through chatSend.
502
+ this.releaseSuppressedSessionKeysAfter(suppressedKeys, 30000);
466
503
  }
467
504
  }
468
505
  /**
@@ -547,6 +584,10 @@ export class OpenClawClient {
547
584
  }
548
585
  async disconnect() {
549
586
  this.shouldReconnect = false;
587
+ for (const timer of this.suppressedSessionTimers.values())
588
+ clearTimeout(timer);
589
+ this.suppressedSessionTimers.clear();
590
+ this.suppressedSessions.clear();
550
591
  if (this.ws) {
551
592
  this.ws.close();
552
593
  this.ws = null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openclaw-lark-multi-agent",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "Multi-bot Lark/Feishu bridge for OpenClaw, with per-bot model routing and isolated sessions",
5
5
  "type": "module",
6
6
  "scripts": {