openclaw-lark-multi-agent 1.2.6 → 1.2.8
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.js +14 -4
- package/dist/live-status.d.ts +13 -0
- package/dist/live-status.js +53 -6
- package/package.json +1 -1
package/dist/feishu-bot.js
CHANGED
|
@@ -1119,6 +1119,13 @@ export class FeishuBot {
|
|
|
1119
1119
|
const isNativeCommandTrigger = firstIsNative;
|
|
1120
1120
|
const lastHuman = mergedTriggers[mergedTriggers.length - 1];
|
|
1121
1121
|
const triggerId = lastHuman.id || 0;
|
|
1122
|
+
// Snapshot the stop generation BEFORE the main run starts. If the user runs
|
|
1123
|
+
// /stop at any point during the main run, handleStopCommand bumps the epoch;
|
|
1124
|
+
// auto-retry compares against this baseline and skips retrying entirely. If
|
|
1125
|
+
// we snapshotted inside auto-retry instead, a /stop that landed during the
|
|
1126
|
+
// main run would already be baked into the baseline and go undetected —
|
|
1127
|
+
// that was the "stop then auto-retry anyway" bug.
|
|
1128
|
+
const runStartStopEpoch = this.stopEpoch.get(chatId) || 0;
|
|
1122
1129
|
const mergedContent = mergedTriggers.map((m) => m.content).join("\n");
|
|
1123
1130
|
const mergedTriggerIds = mergedTriggers.map((m) => m.id || 0).filter(Boolean);
|
|
1124
1131
|
const droppedTriggerIds = droppedMergedTriggers.map((m) => m.id || 0).filter(Boolean);
|
|
@@ -1267,6 +1274,7 @@ export class FeishuBot {
|
|
|
1267
1274
|
isNativeCommandTrigger,
|
|
1268
1275
|
initialReply: reply,
|
|
1269
1276
|
liveStatus,
|
|
1277
|
+
runStartStopEpoch,
|
|
1270
1278
|
});
|
|
1271
1279
|
const parsedReply = this.extractBridgeAttachments(reply);
|
|
1272
1280
|
const visibleReply = parsedReply.text;
|
|
@@ -1780,10 +1788,12 @@ export class FeishuBot {
|
|
|
1780
1788
|
if (!autoRetryEnabled() || isNativeCommandTrigger)
|
|
1781
1789
|
return reply;
|
|
1782
1790
|
const maxRetry = autoRetryMax();
|
|
1783
|
-
//
|
|
1784
|
-
//
|
|
1785
|
-
//
|
|
1786
|
-
|
|
1791
|
+
// Baseline stop generation. Prefer the epoch captured before the main run
|
|
1792
|
+
// (runStartStopEpoch) so a /stop that landed DURING the main run is still
|
|
1793
|
+
// seen here; fall back to the current epoch only if the caller did not pass
|
|
1794
|
+
// one. If the user hit /stop at any point from the main run onward, the
|
|
1795
|
+
// epoch is now past the baseline and we must not auto-retry at all.
|
|
1796
|
+
const startStopEpoch = params.runStartStopEpoch ?? (this.stopEpoch.get(chatId) || 0);
|
|
1787
1797
|
const wasStopped = () => (this.stopEpoch.get(chatId) || 0) !== startStopEpoch;
|
|
1788
1798
|
for (let attempt = 1; attempt <= maxRetry; attempt++) {
|
|
1789
1799
|
// /stop while we were looping: abandon retries and deliver what we have.
|
package/dist/live-status.d.ts
CHANGED
|
@@ -55,6 +55,12 @@ export declare class LiveStatusController {
|
|
|
55
55
|
private createPromise?;
|
|
56
56
|
private finalized;
|
|
57
57
|
private disabled;
|
|
58
|
+
/** Consecutive safeEdit failures. A single transient Feishu error (e.g. card
|
|
59
|
+
* patch code=2200 "Internal Error") must NOT permanently disable the card:
|
|
60
|
+
* we skip that one tick and keep the ticker running, so the next second's
|
|
61
|
+
* patch recovers it. Only after this many CONSECUTIVE failures do we give up.
|
|
62
|
+
* A success resets the counter. */
|
|
63
|
+
private consecutiveEditFailures;
|
|
58
64
|
private state;
|
|
59
65
|
/** Total tool calls (tool_start events) seen during the run, for the summary. */
|
|
60
66
|
private toolCallCount;
|
|
@@ -90,6 +96,13 @@ export declare class LiveStatusController {
|
|
|
90
96
|
/** Refresh the elapsed-time footer periodically even with no new activity. */
|
|
91
97
|
private startTicker;
|
|
92
98
|
private safeEdit;
|
|
99
|
+
/**
|
|
100
|
+
* Terminal edits (complete/fail/noReply) are one-shot — there is no next tick to
|
|
101
|
+
* recover them — so a transient Feishu error (code=2200 etc.) would leave the
|
|
102
|
+
* card stuck on "running". Retry the final patch a few times with small backoff
|
|
103
|
+
* so the card reliably reaches its terminal state.
|
|
104
|
+
*/
|
|
105
|
+
private safeEditFinal;
|
|
93
106
|
/** Dedupe key includes elapsed so the footer timer ticks even without new
|
|
94
107
|
* activity, while identical content within the same second is still skipped. */
|
|
95
108
|
private signature;
|
package/dist/live-status.js
CHANGED
|
@@ -6,6 +6,12 @@ const DEFAULT_HISTORY = Number(process.env.OPENCLAW_LARK_MULTI_AGENT_LIVE_STATUS
|
|
|
6
6
|
// so the "已用时间" ticks like a live timer. Interactive-card patch has no 20-edit
|
|
7
7
|
// cap (unlike text edit), so a 1s cadence is fine.
|
|
8
8
|
const DEFAULT_TICK_MS = Number(process.env.OPENCLAW_LARK_MULTI_AGENT_LIVE_STATUS_TICK_MS || 1000);
|
|
9
|
+
// How many CONSECUTIVE card-edit failures to tolerate before giving up on the
|
|
10
|
+
// card. Feishu's im.message.patch can intermittently return code=2200
|
|
11
|
+
// "Internal Error" (a transient server-side error); a single failure must not
|
|
12
|
+
// freeze the live-status card for the rest of a long run. We skip the failed
|
|
13
|
+
// tick, keep the ticker alive, and only disable after this many in a row.
|
|
14
|
+
const MAX_CONSECUTIVE_EDIT_FAILURES = Number(process.env.OPENCLAW_LARK_MULTI_AGENT_LIVE_STATUS_MAX_EDIT_FAILURES || 5);
|
|
9
15
|
export class LiveStatusController {
|
|
10
16
|
callbacks;
|
|
11
17
|
opts;
|
|
@@ -19,6 +25,12 @@ export class LiveStatusController {
|
|
|
19
25
|
createPromise;
|
|
20
26
|
finalized = false;
|
|
21
27
|
disabled = false;
|
|
28
|
+
/** Consecutive safeEdit failures. A single transient Feishu error (e.g. card
|
|
29
|
+
* patch code=2200 "Internal Error") must NOT permanently disable the card:
|
|
30
|
+
* we skip that one tick and keep the ticker running, so the next second's
|
|
31
|
+
* patch recovers it. Only after this many CONSECUTIVE failures do we give up.
|
|
32
|
+
* A success resets the counter. */
|
|
33
|
+
consecutiveEditFailures = 0;
|
|
22
34
|
state = "running";
|
|
23
35
|
/** Total tool calls (tool_start events) seen during the run, for the summary. */
|
|
24
36
|
toolCallCount = 0;
|
|
@@ -85,7 +97,7 @@ export class LiveStatusController {
|
|
|
85
97
|
await this.createPromise.catch(() => { });
|
|
86
98
|
if (!this.messageId || this.disabled)
|
|
87
99
|
return;
|
|
88
|
-
await this.
|
|
100
|
+
await this.safeEditFinal(this.buildView());
|
|
89
101
|
}
|
|
90
102
|
async fail() {
|
|
91
103
|
this.state = "failed";
|
|
@@ -95,7 +107,7 @@ export class LiveStatusController {
|
|
|
95
107
|
await this.createPromise.catch(() => { });
|
|
96
108
|
if (!this.messageId || this.disabled)
|
|
97
109
|
return;
|
|
98
|
-
await this.
|
|
110
|
+
await this.safeEditFinal(this.buildView());
|
|
99
111
|
}
|
|
100
112
|
/**
|
|
101
113
|
* Finish when the model explicitly produced no reply (NO_REPLY) or an empty
|
|
@@ -111,7 +123,7 @@ export class LiveStatusController {
|
|
|
111
123
|
await this.createPromise.catch(() => { });
|
|
112
124
|
if (!this.messageId || this.disabled)
|
|
113
125
|
return;
|
|
114
|
-
await this.
|
|
126
|
+
await this.safeEditFinal(this.buildView());
|
|
115
127
|
}
|
|
116
128
|
dispose() {
|
|
117
129
|
this.finalized = true;
|
|
@@ -216,15 +228,50 @@ export class LiveStatusController {
|
|
|
216
228
|
try {
|
|
217
229
|
await this.callbacks.edit(this.messageId, view);
|
|
218
230
|
this.lastSentSignature = sig;
|
|
231
|
+
this.consecutiveEditFailures = 0; // recovered
|
|
219
232
|
return true;
|
|
220
233
|
}
|
|
221
234
|
catch (err) {
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
this
|
|
235
|
+
// A single transient Feishu error (e.g. card patch code=2200 "Internal
|
|
236
|
+
// Error", rate limit, network blip) must NOT permanently freeze the card.
|
|
237
|
+
// Skip this tick and keep the ticker running so the next patch recovers it.
|
|
238
|
+
// Only give up after several CONSECUTIVE failures (a persistent problem,
|
|
239
|
+
// e.g. the message was deleted or we truly lost access).
|
|
240
|
+
this.consecutiveEditFailures++;
|
|
241
|
+
const giveUp = this.consecutiveEditFailures >= MAX_CONSECUTIVE_EDIT_FAILURES;
|
|
242
|
+
this.callbacks.warn?.(`live status edit failed (${this.consecutiveEditFailures}/${MAX_CONSECUTIVE_EDIT_FAILURES}${giveUp ? ", disabling" : ", will retry next tick"})`, err);
|
|
243
|
+
if (giveUp) {
|
|
244
|
+
this.disabled = true;
|
|
245
|
+
this.stopTimers();
|
|
246
|
+
}
|
|
225
247
|
return false;
|
|
226
248
|
}
|
|
227
249
|
}
|
|
250
|
+
/**
|
|
251
|
+
* Terminal edits (complete/fail/noReply) are one-shot — there is no next tick to
|
|
252
|
+
* recover them — so a transient Feishu error (code=2200 etc.) would leave the
|
|
253
|
+
* card stuck on "running". Retry the final patch a few times with small backoff
|
|
254
|
+
* so the card reliably reaches its terminal state.
|
|
255
|
+
*/
|
|
256
|
+
async safeEditFinal(view) {
|
|
257
|
+
if (!this.messageId || this.disabled)
|
|
258
|
+
return;
|
|
259
|
+
const attempts = Math.max(1, MAX_CONSECUTIVE_EDIT_FAILURES);
|
|
260
|
+
for (let i = 0; i < attempts; i++) {
|
|
261
|
+
try {
|
|
262
|
+
await this.callbacks.edit(this.messageId, view);
|
|
263
|
+
this.lastSentSignature = this.signature(view);
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
catch (err) {
|
|
267
|
+
const last = i === attempts - 1;
|
|
268
|
+
this.callbacks.warn?.(`live status final edit failed (${i + 1}/${attempts}${last ? ", giving up" : ", retrying"})`, err);
|
|
269
|
+
if (last)
|
|
270
|
+
return;
|
|
271
|
+
await new Promise((r) => setTimeout(r, 300 * (i + 1)));
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
228
275
|
/** Dedupe key includes elapsed so the footer timer ticks even without new
|
|
229
276
|
* activity, while identical content within the same second is still skipped. */
|
|
230
277
|
signature(view) {
|