openclaw-lark-multi-agent 1.2.4 → 1.2.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.
- package/dist/feishu-bot.d.ts +3 -8
- package/dist/feishu-bot.js +24 -114
- package/package.json +1 -1
package/dist/feishu-bot.d.ts
CHANGED
|
@@ -122,14 +122,6 @@ export declare class FeishuBot {
|
|
|
122
122
|
private isBridgeControlReply;
|
|
123
123
|
private isLegacyBridgeControlMessage;
|
|
124
124
|
private isRuntimeFailureText;
|
|
125
|
-
/**
|
|
126
|
-
* Is this main-run error worth auto-retrying (by resuming the session via the
|
|
127
|
-
* probe loop)? Mainly transient failures — request/response timeouts and
|
|
128
|
-
* aborted/incomplete runs. We do NOT auto-retry clearly terminal errors
|
|
129
|
-
* (auth/permission/quota/billing/model-not-supported), which would just fail
|
|
130
|
-
* again. The error text comes from collectReply's `Agent error: <msg>`.
|
|
131
|
-
*/
|
|
132
|
-
private isRetryableAgentError;
|
|
133
125
|
/**
|
|
134
126
|
* Heuristic: does this reply look truncated / mid-task? Bias toward false
|
|
135
127
|
* positives (“宁可错杀不放过”) — a false positive only costs one invisible
|
|
@@ -182,6 +174,9 @@ export declare class FeishuBot {
|
|
|
182
174
|
* session kept working and its new output becomes the latest reply, which we
|
|
183
175
|
* re-check. Loops until the session confirms completion, a reply no longer
|
|
184
176
|
* looks truncated, the budget is spent, or the session cannot answer.
|
|
177
|
+
*
|
|
178
|
+
* This is the ONLY auto-retry trigger: a reply that LOOKS UNFINISHED. We do not
|
|
179
|
+
* retry on transient run errors, and we do not auto-compact-and-retry.
|
|
185
180
|
*/
|
|
186
181
|
private autoRetryUntilComplete;
|
|
187
182
|
private cancelDelayedFailure;
|
package/dist/feishu-bot.js
CHANGED
|
@@ -29,10 +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 an auto-retry probe errors AND context usage is at/above this percent,
|
|
33
|
-
// auto-compact the session once and keep retrying (instead of giving up). This
|
|
34
|
-
// rescues the “session too heavy → run times out / lock contention” failure mode.
|
|
35
|
-
function autoRetryCompactPct() { return Number(process.env.OPENCLAW_LARK_MULTI_AGENT_AUTO_RETRY_COMPACT_PCT || 50); }
|
|
36
32
|
// How many of the most-recent tool calls (and their results) tool-trim keeps
|
|
37
33
|
// intact — recent tool calls may still be relevant to the agent's next step.
|
|
38
34
|
function toolTrimKeepRecent() { return Number(process.env.OPENCLAW_LARK_MULTI_AGENT_TOOLTRIM_KEEP_RECENT || 3); }
|
|
@@ -1248,29 +1244,9 @@ export class FeishuBot {
|
|
|
1248
1244
|
}));
|
|
1249
1245
|
}
|
|
1250
1246
|
catch (mainErr) {
|
|
1251
|
-
// The main run failed before returning a reply.
|
|
1252
|
-
//
|
|
1253
|
-
|
|
1254
|
-
// where it was interrupted, instead of giving up immediately.
|
|
1255
|
-
if (mainErr instanceof UnhealthySessionError)
|
|
1256
|
-
throw mainErr;
|
|
1257
|
-
const errText = mainErr?.message || String(mainErr);
|
|
1258
|
-
if (autoRetryEnabled() && !isNativeCommandTrigger && this.isRetryableAgentError(errText)) {
|
|
1259
|
-
console.warn(`[${this.config.name}] main run failed (${errText.slice(0, 80)}) for ${chatId.slice(-8)}; entering auto-retry`);
|
|
1260
|
-
reply = await this.autoRetryUntilComplete({
|
|
1261
|
-
chatId,
|
|
1262
|
-
sessionKey,
|
|
1263
|
-
senderName: lastHuman.senderName,
|
|
1264
|
-
triggerId,
|
|
1265
|
-
lastHumanMessageId: lastHuman.messageId || "",
|
|
1266
|
-
isNativeCommandTrigger,
|
|
1267
|
-
initialError: errText,
|
|
1268
|
-
liveStatus,
|
|
1269
|
-
});
|
|
1270
|
-
}
|
|
1271
|
-
else {
|
|
1272
|
-
throw mainErr;
|
|
1273
|
-
}
|
|
1247
|
+
// The main run failed before returning a reply. Surface the error; we
|
|
1248
|
+
// only auto-retry replies that look truncated, not transient run errors.
|
|
1249
|
+
throw mainErr;
|
|
1274
1250
|
}
|
|
1275
1251
|
finally {
|
|
1276
1252
|
// OpenClaw may emit the final assistant session.message just after
|
|
@@ -1670,21 +1646,6 @@ export class FeishuBot {
|
|
|
1670
1646
|
isRuntimeFailureText(text) {
|
|
1671
1647
|
return text.startsWith("⚠️ Agent 未正常完成") || /\n原因:\s*rpc\b/.test(text);
|
|
1672
1648
|
}
|
|
1673
|
-
/**
|
|
1674
|
-
* Is this main-run error worth auto-retrying (by resuming the session via the
|
|
1675
|
-
* probe loop)? Mainly transient failures — request/response timeouts and
|
|
1676
|
-
* aborted/incomplete runs. We do NOT auto-retry clearly terminal errors
|
|
1677
|
-
* (auth/permission/quota/billing/model-not-supported), which would just fail
|
|
1678
|
-
* again. The error text comes from collectReply's `Agent error: <msg>`.
|
|
1679
|
-
*/
|
|
1680
|
-
isRetryableAgentError(errText) {
|
|
1681
|
-
const t = (errText || "").toLowerCase();
|
|
1682
|
-
// Terminal errors: never auto-retry.
|
|
1683
|
-
if (/(unauthorized|permission|forbidden|invalid api key|quota|billing|insufficient|model_not_supported|not supported|invalid_request)/.test(t))
|
|
1684
|
-
return false;
|
|
1685
|
-
// Retryable: timeouts and transient interruptions.
|
|
1686
|
-
return /(timed out|timeout|aborted|operation was aborted|no response|request failed|temporarily|connection|econnreset|socket hang|stream)/.test(t);
|
|
1687
|
-
}
|
|
1688
1649
|
/**
|
|
1689
1650
|
* Heuristic: does this reply look truncated / mid-task? Bias toward false
|
|
1690
1651
|
* positives (“宁可错杀不放过”) — a false positive only costs one invisible
|
|
@@ -1808,21 +1769,16 @@ export class FeishuBot {
|
|
|
1808
1769
|
* session kept working and its new output becomes the latest reply, which we
|
|
1809
1770
|
* re-check. Loops until the session confirms completion, a reply no longer
|
|
1810
1771
|
* looks truncated, the budget is spent, or the session cannot answer.
|
|
1772
|
+
*
|
|
1773
|
+
* This is the ONLY auto-retry trigger: a reply that LOOKS UNFINISHED. We do not
|
|
1774
|
+
* retry on transient run errors, and we do not auto-compact-and-retry.
|
|
1811
1775
|
*/
|
|
1812
1776
|
async autoRetryUntilComplete(params) {
|
|
1813
1777
|
const { chatId, sessionKey, senderName, triggerId, lastHumanMessageId, isNativeCommandTrigger, liveStatus } = params;
|
|
1814
1778
|
let reply = params.initialReply ?? "";
|
|
1815
|
-
|
|
1816
|
-
// The timeout path starts in this state (no reply to inspect yet).
|
|
1817
|
-
let forceProbe = params.initialError !== undefined && !params.initialReply;
|
|
1818
|
-
if (!autoRetryEnabled() || isNativeCommandTrigger) {
|
|
1819
|
-
if (forceProbe)
|
|
1820
|
-
throw new Error(`Agent error: ${params.initialError}`);
|
|
1779
|
+
if (!autoRetryEnabled() || isNativeCommandTrigger)
|
|
1821
1780
|
return reply;
|
|
1822
|
-
}
|
|
1823
1781
|
const maxRetry = autoRetryMax();
|
|
1824
|
-
let compacted = false; // auto-compact at most once per auto-retry round
|
|
1825
|
-
let probedAtLeastOnce = false;
|
|
1826
1782
|
// Snapshot the stop generation at entry. If the user runs /stop while this
|
|
1827
1783
|
// loop is in flight, handleStopCommand bumps the epoch; we detect the change
|
|
1828
1784
|
// and stop retrying immediately instead of issuing more probes.
|
|
@@ -1832,26 +1788,21 @@ export class FeishuBot {
|
|
|
1832
1788
|
// /stop while we were looping: abandon retries and deliver what we have.
|
|
1833
1789
|
if (wasStopped()) {
|
|
1834
1790
|
console.log(`[${this.config.name}] auto-retry: aborted by /stop for ${chatId.slice(-8)}`);
|
|
1835
|
-
if (!reply)
|
|
1836
|
-
throw new Error(`Agent error: ${params.initialError || "stopped by user"}`);
|
|
1837
1791
|
return reply;
|
|
1838
1792
|
}
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
return reply;
|
|
1853
|
-
}
|
|
1854
|
-
forceProbe = false; // consumed; subsequent rounds inspect the probe reply
|
|
1793
|
+
const parsed = this.extractBridgeAttachments(reply);
|
|
1794
|
+
const trimmed = parsed.text.trim();
|
|
1795
|
+
// Hard rule: a reply that produced attachments (image/file/doc) is a
|
|
1796
|
+
// finished product — never auto-retry it (and never risk dropping the
|
|
1797
|
+
// bridge attachment marker by replacing `reply` with a probe answer).
|
|
1798
|
+
if (parsed.attachments.length > 0)
|
|
1799
|
+
return reply;
|
|
1800
|
+
// Only engage for plausibly-successful-but-truncated text. Empty/NO_REPLY
|
|
1801
|
+
// and runtime failures have their own handling upstream.
|
|
1802
|
+
if (!trimmed || trimmed.toUpperCase() === "NO_REPLY" || this.isRuntimeFailureText(trimmed))
|
|
1803
|
+
return reply;
|
|
1804
|
+
if (!this.looksTruncated(trimmed))
|
|
1805
|
+
return reply;
|
|
1855
1806
|
const en = this.isEn(chatId);
|
|
1856
1807
|
await liveStatus?.progress(en
|
|
1857
1808
|
? `⚠️ Reply looks incomplete — auto-checking (${attempt}/${maxRetry})…`
|
|
@@ -1875,41 +1826,11 @@ export class FeishuBot {
|
|
|
1875
1826
|
includeBridgeAttachmentHint: false,
|
|
1876
1827
|
onProgress: (event) => liveStatus?.progress(event),
|
|
1877
1828
|
}));
|
|
1878
|
-
probedAtLeastOnce = true;
|
|
1879
1829
|
}
|
|
1880
1830
|
catch (err) {
|
|
1881
|
-
//
|
|
1882
|
-
//
|
|
1883
|
-
// keep retrying — a bloated session is the usual cause of these timeouts.
|
|
1884
|
-
// Otherwise stop looping.
|
|
1831
|
+
// The confirmation round itself failed (timeout/unhealthy). Do not spin:
|
|
1832
|
+
// deliver the existing (truncated-looking) reply as-is.
|
|
1885
1833
|
console.warn(`[${this.config.name}] auto-retry probe failed for ${chatId.slice(-8)}:`, this.errorSummary(err));
|
|
1886
|
-
if (!compacted) {
|
|
1887
|
-
const pct = await this.getContextUsagePercent(sessionKey);
|
|
1888
|
-
if (pct >= autoRetryCompactPct()) {
|
|
1889
|
-
const en = this.isEn(chatId);
|
|
1890
|
-
await liveStatus?.progress(en
|
|
1891
|
-
? `🧹 Context is large (${pct}%) and the retry failed — auto-compacting…`
|
|
1892
|
-
: `🧹 上下文过大(${pct}%)且重试出错,正在自动压缩…`).catch(() => { });
|
|
1893
|
-
try {
|
|
1894
|
-
const r = await this.compactWithFallback(sessionKey);
|
|
1895
|
-
compacted = true; // mark as attempted regardless, to avoid re-compacting in a loop
|
|
1896
|
-
if (r.compacted) {
|
|
1897
|
-
console.log(`[${this.config.name}] auto-retry: compacted session (${r.method}${r.detail ? " " + r.detail : ""}) at ${pct}% for ${chatId.slice(-8)}; continuing`);
|
|
1898
|
-
forceProbe = !reply; // if we still have no reply (timeout path), keep probing
|
|
1899
|
-
continue; // retry the probe on the now-lighter session
|
|
1900
|
-
}
|
|
1901
|
-
// Compact was a no-op; do not spin.
|
|
1902
|
-
console.warn(`[${this.config.name}] auto-retry: compact was a no-op for ${chatId.slice(-8)} (${r.reason || "no reason"})`);
|
|
1903
|
-
}
|
|
1904
|
-
catch (compactErr) {
|
|
1905
|
-
console.warn(`[${this.config.name}] auto-retry compact failed for ${chatId.slice(-8)}:`, this.errorSummary(compactErr));
|
|
1906
|
-
}
|
|
1907
|
-
}
|
|
1908
|
-
}
|
|
1909
|
-
// No reply to fall back to (timeout path) and retries exhausted/failed:
|
|
1910
|
-
// re-throw so the caller surfaces the original failure.
|
|
1911
|
-
if (!reply)
|
|
1912
|
-
throw (err instanceof Error ? err : new Error(`Agent error: ${params.initialError || "retry failed"}`));
|
|
1913
1834
|
return reply;
|
|
1914
1835
|
}
|
|
1915
1836
|
const probeText = this.extractBridgeAttachments(probeReply).text.trim();
|
|
@@ -1917,31 +1838,20 @@ export class FeishuBot {
|
|
|
1917
1838
|
// again. Deliver the latest meaningful output we have.
|
|
1918
1839
|
if (wasStopped()) {
|
|
1919
1840
|
console.log(`[${this.config.name}] auto-retry: aborted by /stop mid-probe for ${chatId.slice(-8)}`);
|
|
1920
|
-
|
|
1921
|
-
if (!latest)
|
|
1922
|
-
throw new Error(`Agent error: ${params.initialError || "stopped by user"}`);
|
|
1923
|
-
return latest;
|
|
1841
|
+
return probeText && probeText.toUpperCase() !== "NO_REPLY" ? probeReply : reply;
|
|
1924
1842
|
}
|
|
1925
1843
|
if (!probeText || probeText.toUpperCase() === "NO_REPLY") {
|
|
1926
|
-
if (!reply)
|
|
1927
|
-
throw new Error(`Agent error: ${params.initialError || "no reply after retry"}`);
|
|
1928
1844
|
return reply; // session could not answer; deliver existing result
|
|
1929
1845
|
}
|
|
1930
1846
|
if (this.isDonePhrase(probeText)) {
|
|
1931
1847
|
console.log(`[${this.config.name}] auto-retry: session confirmed done after ${attempt} check(s) for ${chatId.slice(-8)}`);
|
|
1932
|
-
|
|
1933
|
-
// means the interrupted work actually finished — but we have nothing
|
|
1934
|
-
// user-visible to deliver, so treat it as NO_REPLY rather than fabricate.
|
|
1935
|
-
return reply || "NO_REPLY";
|
|
1848
|
+
return reply;
|
|
1936
1849
|
}
|
|
1937
1850
|
// Not done: the session kept working. Its output is the new latest reply.
|
|
1938
1851
|
console.log(`[${this.config.name}] auto-retry: session continued (attempt ${attempt}) for ${chatId.slice(-8)}`);
|
|
1939
1852
|
reply = probeReply;
|
|
1940
1853
|
}
|
|
1941
1854
|
console.warn(`[${this.config.name}] auto-retry budget (${maxRetry}) spent for ${chatId.slice(-8)}; delivering latest result`);
|
|
1942
|
-
void probedAtLeastOnce;
|
|
1943
|
-
if (!reply)
|
|
1944
|
-
throw new Error(`Agent error: ${params.initialError || "retry budget exhausted"}`);
|
|
1945
1855
|
return reply;
|
|
1946
1856
|
}
|
|
1947
1857
|
cancelDelayedFailure(chatId) {
|