openclaw-lark-multi-agent 1.2.3 → 1.2.4

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.
@@ -155,46 +155,6 @@ export declare class FeishuBot {
155
155
  * A plain “no sentence-ending punctuation” is deliberately NOT sufficient.
156
156
  */
157
157
  private looksTruncated;
158
- /**
159
- * Did the model type a tool call as plain text instead of emitting a real
160
- * function call? When that happens the visible reply ends with a dangling
161
- * invoke / parameter / function_calls tag (with or without the `antml:`
162
- * prefix), because the tool-call markup leaked into the text channel and the
163
- * tool never actually ran. We detect such a tag anchored at the END of the
164
- * message — that dangling tag is the tell-tale Stephen flagged ("如果监测到
165
- * 这个结尾,说明 tool call 格式出了问题"). On detection the auto-retry loop
166
- * sends an explicit "invoke 格式错了,请重试" probe so the agent re-issues the
167
- * tool call correctly rather than the generic done-check.
168
- */
169
- /**
170
- * Did the model serialize a tool call as plain text instead of emitting a real
171
- * function call? When that happens, the structured tool-call XML leaks into the
172
- * visible text channel and the tool never runs. We must distinguish this from a
173
- * reply that merely *mentions* a tag (teaching, quoting, showing an example),
174
- * which must NOT trigger a retry. So we require strong STRUCTURAL evidence of a
175
- * leaked call, not just any tag at the end:
176
- *
177
- * 1. The reply must END with a closing `</invoke>` (optionally `antml:`).
178
- * A dangling OPEN tag or a bare `</parameter>`/`<function_calls>` alone is
179
- * not enough — too easy to hit in normal prose.
180
- * 2. Somewhere before it there must be a matching `<invoke name="...">` OPEN
181
- * tag — i.e. a whole call block (`<invoke…>…</invoke>`) materialized as
182
- * text. A lone `</invoke>` (e.g. “闭合标签是 </invoke>”) is a mention,
183
- * not a leaked call.
184
- * 3. The trailing `</invoke>` must NOT be wrapped in inline code/backticks or
185
- * a fenced code block — those are deliberate examples, not a leak.
186
- *
187
- * Anchored at the very end because a genuinely leaked call is the LAST thing in
188
- * the message (the model stopped right after “emitting” it). This keeps the
189
- * trigger tight so we do not retry on normal replies that talk about tags.
190
- */
191
- private looksLikeBrokenToolCall;
192
- /**
193
- * Is the tag at `tagIndex` inside an inline-code span (`` `…` ``) or a fenced
194
- * code block (```` ``` ````)? Such tags are deliberate examples, not a leaked
195
- * tool call, so the broken-tool-call detector must ignore them.
196
- */
197
- private isTrailingTagInCode;
198
158
  /** Current context usage percent for a session (totalTokens / contextTokens),
199
159
  * or 0 if it cannot be determined. Same basis as /status. */
200
160
  private getContextUsagePercent;
@@ -29,14 +29,6 @@ function autoRetryMax() { return Number(process.env.OPENCLAW_LARK_MULTI_AGENT_AU
29
29
  // for the locale-appropriate phrase; detection accepts BOTH so either works.
30
30
  const AUTO_RETRY_DONE_PHRASE = process.env.OPENCLAW_LARK_MULTI_AGENT_AUTO_RETRY_DONE_PHRASE || "\u7ed3\u675f\u4e86";
31
31
  const AUTO_RETRY_DONE_PHRASE_EN = process.env.OPENCLAW_LARK_MULTI_AGENT_AUTO_RETRY_DONE_PHRASE_EN || "DONE";
32
- // When the previous reply ended with a dangling tool-call tag (the model typed
33
- // an invoke/tool call as plain text and it never ran), the auto-retry probe
34
- // explicitly tells the agent its invoke format was broken and to retry, instead
35
- // of the generic done-check. It still accepts the done phrase if actually done.
36
- const AUTO_RETRY_BROKEN_TOOL_PROBE = process.env.OPENCLAW_LARK_MULTI_AGENT_AUTO_RETRY_BROKEN_TOOL_PROBE
37
- || `刚才的任务是不是没结束,invoke 格式错了,invoke 格式错了,请重试。如果结束了,回复“${AUTO_RETRY_DONE_PHRASE}”。`;
38
- const AUTO_RETRY_BROKEN_TOOL_PROBE_EN = process.env.OPENCLAW_LARK_MULTI_AGENT_AUTO_RETRY_BROKEN_TOOL_PROBE_EN
39
- || `Your last task did not finish — the invoke format was broken, the invoke format was broken, please retry. If it is actually done, reply "${AUTO_RETRY_DONE_PHRASE_EN}".`;
40
32
  // When an auto-retry probe errors AND context usage is at/above this percent,
41
33
  // auto-compact the session once and keep retrying (instead of giving up). This
42
34
  // rescues the “session too heavy → run times out / lock contention” failure mode.
@@ -1733,76 +1725,6 @@ export class FeishuBot {
1733
1725
  return true;
1734
1726
  return false;
1735
1727
  }
1736
- /**
1737
- * Did the model type a tool call as plain text instead of emitting a real
1738
- * function call? When that happens the visible reply ends with a dangling
1739
- * invoke / parameter / function_calls tag (with or without the `antml:`
1740
- * prefix), because the tool-call markup leaked into the text channel and the
1741
- * tool never actually ran. We detect such a tag anchored at the END of the
1742
- * message — that dangling tag is the tell-tale Stephen flagged ("如果监测到
1743
- * 这个结尾,说明 tool call 格式出了问题"). On detection the auto-retry loop
1744
- * sends an explicit "invoke 格式错了,请重试" probe so the agent re-issues the
1745
- * tool call correctly rather than the generic done-check.
1746
- */
1747
- /**
1748
- * Did the model serialize a tool call as plain text instead of emitting a real
1749
- * function call? When that happens, the structured tool-call XML leaks into the
1750
- * visible text channel and the tool never runs. We must distinguish this from a
1751
- * reply that merely *mentions* a tag (teaching, quoting, showing an example),
1752
- * which must NOT trigger a retry. So we require strong STRUCTURAL evidence of a
1753
- * leaked call, not just any tag at the end:
1754
- *
1755
- * 1. The reply must END with a closing `</invoke>` (optionally `antml:`).
1756
- * A dangling OPEN tag or a bare `</parameter>`/`<function_calls>` alone is
1757
- * not enough — too easy to hit in normal prose.
1758
- * 2. Somewhere before it there must be a matching `<invoke name="...">` OPEN
1759
- * tag — i.e. a whole call block (`<invoke…>…</invoke>`) materialized as
1760
- * text. A lone `</invoke>` (e.g. “闭合标签是 </invoke>”) is a mention,
1761
- * not a leaked call.
1762
- * 3. The trailing `</invoke>` must NOT be wrapped in inline code/backticks or
1763
- * a fenced code block — those are deliberate examples, not a leak.
1764
- *
1765
- * Anchored at the very end because a genuinely leaked call is the LAST thing in
1766
- * the message (the model stopped right after “emitting” it). This keeps the
1767
- * trigger tight so we do not retry on normal replies that talk about tags.
1768
- */
1769
- looksLikeBrokenToolCall(text) {
1770
- const t = (text || "").trim();
1771
- if (!t)
1772
- return false;
1773
- // (1) Must end with a closing invoke tag (the serialized call's terminator).
1774
- const closeMatch = /<\/\s*(?:antml:)?invoke\s*>\s*$/i.exec(t);
1775
- if (!closeMatch)
1776
- return false;
1777
- // (3) Reject when that closing tag is fenced or inline-code wrapped (example).
1778
- if (this.isTrailingTagInCode(t, closeMatch.index))
1779
- return false;
1780
- // (2) Require a matching opening `<invoke ...>` before the closer — proof that
1781
- // a whole call block leaked, not just a mention of the closing tag.
1782
- const head = t.slice(0, closeMatch.index);
1783
- if (!/<\s*(?:antml:)?invoke\b[^>]*>/i.test(head))
1784
- return false;
1785
- return true;
1786
- }
1787
- /**
1788
- * Is the tag at `tagIndex` inside an inline-code span (`` `…` ``) or a fenced
1789
- * code block (```` ``` ````)? Such tags are deliberate examples, not a leaked
1790
- * tool call, so the broken-tool-call detector must ignore them.
1791
- */
1792
- isTrailingTagInCode(text, tagIndex) {
1793
- const before = text.slice(0, tagIndex);
1794
- // Fenced block: an odd number of ``` before the tag means we are inside one.
1795
- const fences = (before.match(/```/g) || []).length;
1796
- if (fences % 2 === 1)
1797
- return true;
1798
- // Inline code: an odd number of unescaped backticks on the tag's own line.
1799
- const lineStart = before.lastIndexOf("\n") + 1;
1800
- const lineBefore = before.slice(lineStart);
1801
- const ticks = (lineBefore.match(/`/g) || []).length;
1802
- if (ticks % 2 === 1)
1803
- return true;
1804
- return false;
1805
- }
1806
1728
  /** Current context usage percent for a session (totalTokens / contextTokens),
1807
1729
  * or 0 if it cannot be determined. Same basis as /status. */
1808
1730
  async getContextUsagePercent(sessionKey) {
@@ -1906,10 +1828,6 @@ export class FeishuBot {
1906
1828
  // and stop retrying immediately instead of issuing more probes.
1907
1829
  const startStopEpoch = this.stopEpoch.get(chatId) || 0;
1908
1830
  const wasStopped = () => (this.stopEpoch.get(chatId) || 0) !== startStopEpoch;
1909
- // Set per-iteration when the reply under inspection ended with a dangling
1910
- // tool-call tag (model typed an invoke as plain text); selects the explicit
1911
- // "invoke format was broken, retry" probe instead of the generic done-check.
1912
- let brokenToolCall = false;
1913
1831
  for (let attempt = 1; attempt <= maxRetry; attempt++) {
1914
1832
  // /stop while we were looping: abandon retries and deliver what we have.
1915
1833
  if (wasStopped()) {
@@ -1918,7 +1836,6 @@ export class FeishuBot {
1918
1836
  throw new Error(`Agent error: ${params.initialError || "stopped by user"}`);
1919
1837
  return reply;
1920
1838
  }
1921
- brokenToolCall = false;
1922
1839
  if (!forceProbe) {
1923
1840
  const parsed = this.extractBridgeAttachments(reply);
1924
1841
  const trimmed = parsed.text.trim();
@@ -1931,32 +1848,20 @@ export class FeishuBot {
1931
1848
  // and runtime failures have their own handling upstream.
1932
1849
  if (!trimmed || trimmed.toUpperCase() === "NO_REPLY" || this.isRuntimeFailureText(trimmed))
1933
1850
  return reply;
1934
- // A dangling invoke/tool-call tag is a strong "tool call leaked as text"
1935
- // signal — engage even if the generic truncation heuristic would miss it.
1936
- brokenToolCall = this.looksLikeBrokenToolCall(trimmed);
1937
- if (!this.looksTruncated(trimmed) && !brokenToolCall)
1851
+ if (!this.looksTruncated(trimmed))
1938
1852
  return reply;
1939
1853
  }
1940
1854
  forceProbe = false; // consumed; subsequent rounds inspect the probe reply
1941
1855
  const en = this.isEn(chatId);
1942
1856
  await liveStatus?.progress(en
1943
- ? (brokenToolCall
1944
- ? `⚠️ Tool-call format looked broken — asking it to retry (${attempt}/${maxRetry})…`
1945
- : `⚠️ Reply looks incomplete — auto-checking (${attempt}/${maxRetry})…`)
1946
- : (brokenToolCall
1947
- ? `⚠️ 工具调用格式疑似出错,正在让它重试(${attempt}/${maxRetry})…`
1948
- : `⚠️ 疑似任务未完成,正在自动重试(${attempt}/${maxRetry})…`)).catch(() => { });
1857
+ ? `⚠️ Reply looks incomplete — auto-checking (${attempt}/${maxRetry})…`
1858
+ : `⚠️ 疑似任务未完成,正在自动重试(${attempt}/${maxRetry})…`).catch(() => { });
1949
1859
  // One short line, in the user's own voice (we intentionally do not change
1950
1860
  // senderName). Each locale uses its own done-phrase so the English probe
1951
- // stays fully English; detection accepts both phrases. When the previous
1952
- // reply ended with a dangling tool-call tag, send the explicit "invoke
1953
- // format was broken, retry" probe instead of the generic done-check so the
1954
- // agent re-issues the tool call correctly.
1955
- const probe = brokenToolCall
1956
- ? (en ? AUTO_RETRY_BROKEN_TOOL_PROBE_EN : AUTO_RETRY_BROKEN_TOOL_PROBE)
1957
- : (en
1958
- ? `Is that done? If so just reply "${AUTO_RETRY_DONE_PHRASE_EN}", otherwise keep going.`
1959
- : `刚才那个任务结束了吗?结束了就回我“${AUTO_RETRY_DONE_PHRASE}”,没结束就接着做。`);
1861
+ // stays fully English; detection accepts both phrases.
1862
+ const probe = en
1863
+ ? `Is that done? If so just reply "${AUTO_RETRY_DONE_PHRASE_EN}", otherwise keep going.`
1864
+ : `刚才那个任务结束了吗?结束了就回我“${AUTO_RETRY_DONE_PHRASE}”,没结束就接着做。`;
1960
1865
  let probeReply;
1961
1866
  try {
1962
1867
  probeReply = await this.withSessionHealthMonitor(chatId, lastHumanMessageId, triggerId, this.openclawClient.chatSendWithContext({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openclaw-lark-multi-agent",
3
- "version": "1.2.3",
3
+ "version": "1.2.4",
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": {