openclaw-lark-multi-agent 1.8.12 → 1.8.14
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 +8 -0
- package/dist/feishu-bot.js +62 -1
- package/dist/openclaw-client.js +7 -4
- package/package.json +2 -2
package/dist/feishu-bot.d.ts
CHANGED
|
@@ -354,8 +354,16 @@ export declare class FeishuBot {
|
|
|
354
354
|
/**
|
|
355
355
|
* Enqueue a message send to guarantee ordering per chat.
|
|
356
356
|
* All sends for a chat are serialized through this.
|
|
357
|
+
*
|
|
358
|
+
* The chain must never wedge: a single hung Feishu call used to block every
|
|
359
|
+
* later send in that chat forever (live-status patches fire per tool event),
|
|
360
|
+
* which froze cards and stranded /stop confirmations. Each link is therefore
|
|
361
|
+
* raced against a deadline, so a stuck send is abandoned and ordering resumes.
|
|
362
|
+
* Prefer a larger OPENCLAW_LARK_MULTI_AGENT_SEND_TIMEOUT_MS on slow links.
|
|
357
363
|
*/
|
|
358
364
|
private sendOrdered;
|
|
365
|
+
/** One send link: resolve with fn(), or reject at the deadline so the chain advances. */
|
|
366
|
+
private withSendDeadline;
|
|
359
367
|
/**
|
|
360
368
|
* Add a reaction (emoji) to a message.
|
|
361
369
|
*/
|
package/dist/feishu-bot.js
CHANGED
|
@@ -29,6 +29,9 @@ const SESSION_HEALTH_POLL_MS = Number(process.env.OPENCLAW_LARK_MULTI_AGENT_SESS
|
|
|
29
29
|
const SESSION_HEALTH_CONFIRM_MS = Number(process.env.OPENCLAW_LARK_MULTI_AGENT_SESSION_HEALTH_CONFIRM_MS || 2_000);
|
|
30
30
|
const DELIVERY_MAX_ATTEMPTS = Number(process.env.OPENCLAW_LARK_MULTI_AGENT_DELIVERY_MAX_ATTEMPTS || 5);
|
|
31
31
|
const DELIVERY_RETRY_BASE_MS = Number(process.env.OPENCLAW_LARK_MULTI_AGENT_DELIVERY_RETRY_BASE_MS || 1_000);
|
|
32
|
+
// Per-chat send serialization deadline. A hung Feishu call must not block the
|
|
33
|
+
// chain forever, otherwise live-status patches and /stop confirmations strand.
|
|
34
|
+
const SEND_DEADLINE_MS = Number(process.env.OPENCLAW_LARK_MULTI_AGENT_SEND_TIMEOUT_MS || 30_000);
|
|
32
35
|
// Auto-retry: when a reply looks truncated/incomplete, ask the same session
|
|
33
36
|
// whether it finished. If it did not, let it continue; loop until it confirms
|
|
34
37
|
// completion or the retry budget is spent. Off by setting AUTO_RETRY=0.
|
|
@@ -1361,10 +1364,20 @@ export class FeishuBot {
|
|
|
1361
1364
|
return;
|
|
1362
1365
|
}
|
|
1363
1366
|
const identified = compatible && !!activity.activeRunId && !!activity.sessionId && !activity.archived && !status.archived;
|
|
1367
|
+
// Bounded retention (2026-10-17 deadlock): an input that has stayed
|
|
1368
|
+
// activity-unbound for over 60s despite re-checks must not be held forever.
|
|
1369
|
+
// Hand it to the ordinary queue — the gateway's own chat.send admission is
|
|
1370
|
+
// the authoritative answer and admission custody already dedups.
|
|
1371
|
+
const unboundSince = current?.activityUnbound ? (current.unboundSince || 0) : 0;
|
|
1372
|
+
if (!identified && unboundSince && Date.now() - unboundSince > 60_000) {
|
|
1373
|
+
this.store.saveInsertion(this.config.name, chatId, rowId, current.insertionId, 'ordinary', { ...current, activityUnbound: false, reason: 'gateway state unconfirmable over 60s; ordinary admission is authoritative' });
|
|
1374
|
+
return;
|
|
1375
|
+
}
|
|
1364
1376
|
this.store.saveInsertion(this.config.name, chatId, rowId, current?.insertionId || JSON.stringify([this.config.appId, chatId, messageId]), 'pending', {
|
|
1365
1377
|
messageId, text, stopEpoch: epoch, sessionKey: this.getSessionKey(chatId), sessionId: identified ? activity.sessionId : status.sessionId,
|
|
1366
1378
|
expectedRunId: identified ? activity.activeRunId : '', ownerId: identified ? `external:${activity.activeRunId}` : undefined,
|
|
1367
1379
|
batchId: identified ? activity.activeRunId : undefined, activityUnbound: !identified, attempted: false,
|
|
1380
|
+
...(identified ? {} : { unboundSince: unboundSince || Date.now() }),
|
|
1368
1381
|
reason: identified ? 'current Gateway run observed; insertion custody only' : 'current Gateway activity/run identity unavailable; retained without admission',
|
|
1369
1382
|
}, Date.now() + (identified ? 0 : 5000));
|
|
1370
1383
|
}
|
|
@@ -1576,6 +1589,21 @@ export class FeishuBot {
|
|
|
1576
1589
|
}
|
|
1577
1590
|
else if (state === 'target_ended')
|
|
1578
1591
|
state = 'pending';
|
|
1592
|
+
else if (state === 'pending') {
|
|
1593
|
+
// Safe recovery for a NON-INSERTABLE target (audit P1 #2
|
|
1594
|
+
// 2026-10-17): this branch only runs for EXPLICIT no-effect
|
|
1595
|
+
// receipts (attempted===false, definitively not inserted — the
|
|
1596
|
+
// outer guard). If the exact target run has settled, or the
|
|
1597
|
+
// target has refused insertion for over 60s (e.g. a long-lived
|
|
1598
|
+
// maintenance run), hand the input to the ordinary queue: the
|
|
1599
|
+
// gateway queues it behind whatever currently holds the session
|
|
1600
|
+
// instead of parking it in bridge-side limbo forever. Unknown
|
|
1601
|
+
// receipts never enter this branch and are never replayed.
|
|
1602
|
+
const boundSince = Number.isFinite(item.boundSince) ? item.boundSince : Date.now();
|
|
1603
|
+
item.boundSince = boundSince;
|
|
1604
|
+
if (originalRunEnded || Date.now() - boundSince > 60_000)
|
|
1605
|
+
state = 'ordinary';
|
|
1606
|
+
}
|
|
1579
1607
|
}
|
|
1580
1608
|
if (state === 'target_ended')
|
|
1581
1609
|
state = 'rejected'; // prior explicit backend refusal; never silently move it to a successor
|
|
@@ -4628,13 +4656,46 @@ export class FeishuBot {
|
|
|
4628
4656
|
/**
|
|
4629
4657
|
* Enqueue a message send to guarantee ordering per chat.
|
|
4630
4658
|
* All sends for a chat are serialized through this.
|
|
4659
|
+
*
|
|
4660
|
+
* The chain must never wedge: a single hung Feishu call used to block every
|
|
4661
|
+
* later send in that chat forever (live-status patches fire per tool event),
|
|
4662
|
+
* which froze cards and stranded /stop confirmations. Each link is therefore
|
|
4663
|
+
* raced against a deadline, so a stuck send is abandoned and ordering resumes.
|
|
4664
|
+
* Prefer a larger OPENCLAW_LARK_MULTI_AGENT_SEND_TIMEOUT_MS on slow links.
|
|
4631
4665
|
*/
|
|
4632
4666
|
sendOrdered(chatId, fn) {
|
|
4633
4667
|
const prev = this.sendQueue.get(chatId) || Promise.resolve();
|
|
4634
|
-
const
|
|
4668
|
+
const run = () => this.withSendDeadline(fn, chatId);
|
|
4669
|
+
const next = prev.then(run, run); // run even if previous failed
|
|
4635
4670
|
this.sendQueue.set(chatId, next.then(() => undefined, () => undefined));
|
|
4636
4671
|
return next;
|
|
4637
4672
|
}
|
|
4673
|
+
/** One send link: resolve with fn(), or reject at the deadline so the chain advances. */
|
|
4674
|
+
async withSendDeadline(fn, chatId) {
|
|
4675
|
+
const limit = SEND_DEADLINE_MS;
|
|
4676
|
+
let work;
|
|
4677
|
+
try {
|
|
4678
|
+
work = Promise.resolve(fn());
|
|
4679
|
+
}
|
|
4680
|
+
catch (err) {
|
|
4681
|
+
return Promise.reject(err);
|
|
4682
|
+
}
|
|
4683
|
+
// A late rejection from an abandoned send must never become an unhandled
|
|
4684
|
+
// rejection; the caller already moved on with the timeout error.
|
|
4685
|
+
work.catch(() => { });
|
|
4686
|
+
let timer;
|
|
4687
|
+
const deadline = new Promise((_, reject) => {
|
|
4688
|
+
timer = setTimeout(() => reject(new Error(`send deadline ${limit}ms exceeded for ${chatId.slice(-8)}`)), limit);
|
|
4689
|
+
timer.unref?.();
|
|
4690
|
+
});
|
|
4691
|
+
try {
|
|
4692
|
+
return await Promise.race([work, deadline]);
|
|
4693
|
+
}
|
|
4694
|
+
finally {
|
|
4695
|
+
if (timer)
|
|
4696
|
+
clearTimeout(timer);
|
|
4697
|
+
}
|
|
4698
|
+
}
|
|
4638
4699
|
/**
|
|
4639
4700
|
* Add a reaction (emoji) to a message.
|
|
4640
4701
|
*/
|
package/dist/openclaw-client.js
CHANGED
|
@@ -2177,8 +2177,8 @@ export class OpenClawClient {
|
|
|
2177
2177
|
let timer;
|
|
2178
2178
|
try {
|
|
2179
2179
|
const value = await Promise.race([
|
|
2180
|
-
this.rpc("sessions.describe", { key: this.canonicalSessionKey(sessionKey) },
|
|
2181
|
-
new Promise((_, reject) => { timer = setTimeout(() => reject(new Error("status lookup timeout")),
|
|
2180
|
+
this.rpc("sessions.describe", { key: this.canonicalSessionKey(sessionKey) }, 5000),
|
|
2181
|
+
new Promise((_, reject) => { timer = setTimeout(() => reject(new Error("status lookup timeout")), 5000); }),
|
|
2182
2182
|
]);
|
|
2183
2183
|
return normalizeSessionRuntimeStatus(value);
|
|
2184
2184
|
}
|
|
@@ -2194,7 +2194,7 @@ export class OpenClawClient {
|
|
|
2194
2194
|
* terminal caches. Unknown/missing fields never authorize scheduling. */
|
|
2195
2195
|
async getSessionDispatchStatus(sessionKey) {
|
|
2196
2196
|
let timer;
|
|
2197
|
-
const key = this.canonicalSessionKey(sessionKey), deadline = Date.now() +
|
|
2197
|
+
const key = this.canonicalSessionKey(sessionKey), deadline = Date.now() + 8000;
|
|
2198
2198
|
// One budget covers the entire public pending scan, not 1500ms per page.
|
|
2199
2199
|
// Retained interrupted/cancelled inputs are recovery records, NOT runnable
|
|
2200
2200
|
// queue entries. Never delete, mutate or replay them to prove schedulability.
|
|
@@ -2286,8 +2286,11 @@ export class OpenClawClient {
|
|
|
2286
2286
|
return unknown();
|
|
2287
2287
|
};
|
|
2288
2288
|
try {
|
|
2289
|
+
// The outer race must honor the SAME total budget as the scan itself
|
|
2290
|
+
// (audit P1 2026-10-17: an 8000ms scan raced against a 1500ms timer was
|
|
2291
|
+
// still killed at 1500ms — three real 650ms pages never got to finish).
|
|
2289
2292
|
return await Promise.race([scan(), new Promise((_, reject) => {
|
|
2290
|
-
timer = setTimeout(() => reject(new Error('activity lookup timeout')),
|
|
2293
|
+
timer = setTimeout(() => reject(new Error('activity lookup timeout')), 8000);
|
|
2291
2294
|
})]);
|
|
2292
2295
|
}
|
|
2293
2296
|
catch {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openclaw-lark-multi-agent",
|
|
3
|
-
"version": "1.8.
|
|
3
|
+
"version": "1.8.14",
|
|
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": {
|
|
@@ -83,4 +83,4 @@
|
|
|
83
83
|
"publishConfig": {
|
|
84
84
|
"access": "public"
|
|
85
85
|
}
|
|
86
|
-
}
|
|
86
|
+
}
|