openclaw-lark-multi-agent 1.8.0 → 1.8.1
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 +59 -4
- package/dist/openclaw-client.d.ts +1 -1
- package/dist/openclaw-client.js +4 -0
- package/package.json +1 -1
package/dist/feishu-bot.d.ts
CHANGED
|
@@ -137,6 +137,14 @@ export declare class FeishuBot {
|
|
|
137
137
|
/** Fire and clear run-failure observers for a chat (isolated callbacks). */
|
|
138
138
|
private fireRunFailureObservers;
|
|
139
139
|
private processQueue;
|
|
140
|
+
/**
|
|
141
|
+
* Force-release a stuck queue owner after the gateway authoritatively
|
|
142
|
+
* reported the session idle (no active run). The zombie owner's deep waits
|
|
143
|
+
* can otherwise hold chat custody forever: completion events routed through
|
|
144
|
+
* the proactive path are invisible to them. Steer-fallback triggers are
|
|
145
|
+
* restored via the failure observers so their input drains normally.
|
|
146
|
+
*/
|
|
147
|
+
private forceExpireQueueOwner;
|
|
140
148
|
private processQueueInner;
|
|
141
149
|
private shouldHandleBridgeCommand;
|
|
142
150
|
private shouldRespond;
|
package/dist/feishu-bot.js
CHANGED
|
@@ -1286,7 +1286,39 @@ export class FeishuBot {
|
|
|
1286
1286
|
stopWatchingConsumption();
|
|
1287
1287
|
if ("cancelPending" in steer)
|
|
1288
1288
|
steer.cancelPending?.();
|
|
1289
|
-
|
|
1289
|
+
// Zombie-owner detection (2026-10-15 guangzi incident): steer found no
|
|
1290
|
+
// active run while our in-memory owner still holds the chat. Ask the
|
|
1291
|
+
// gateway for the authoritative session status: if it is idle, the owner
|
|
1292
|
+
// is a zombie (its completion event was routed through the proactive
|
|
1293
|
+
// path and it will never settle). Force-expire it and process this input
|
|
1294
|
+
// NOW instead of queueing behind a dead promise forever.
|
|
1295
|
+
if (steer.status === "no_active_run" && this.queueRuns.has(chatId)) {
|
|
1296
|
+
const live = await this.openclawClient.getSessionRuntimeStatus(sessionKeyForSteer).catch(() => null);
|
|
1297
|
+
if (live && !live.running) {
|
|
1298
|
+
console.warn(`[${this.config.name}] gateway reports session idle while queue owner exists for ${chatId.slice(-8)} — expiring zombie owner (status=${live.status})`);
|
|
1299
|
+
this.forceExpireQueueOwner(chatId, `gateway idle (steer=${steer.status}, session=${live.status})`);
|
|
1300
|
+
// Fall through to the normal send path below; the durable trigger
|
|
1301
|
+
// for this input is still set, so the fresh processQueue drains it.
|
|
1302
|
+
}
|
|
1303
|
+
else {
|
|
1304
|
+
console.log(`[${this.config.name}] Agent busy for ${chatId.slice(-8)} (steer=${steer.status}, session running=${live?.running}), queuing: "${cleanText.substring(0, 50)}..."`);
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
else {
|
|
1308
|
+
console.log(`[${this.config.name}] Agent busy for ${chatId.slice(-8)} (steer=${steer.status}), queuing: "${cleanText.substring(0, 50)}..."`);
|
|
1309
|
+
}
|
|
1310
|
+
if (!this.queueRuns.has(chatId)) {
|
|
1311
|
+
// Owner gone (force-expired or settled during the steer RPC): send now.
|
|
1312
|
+
await this.removeReaction(messageId, "Typing").catch(() => { });
|
|
1313
|
+
await this.addReaction(messageId, "Get").catch(() => { });
|
|
1314
|
+
const acksNow = this.pendingAckMessages.get(chatId) || [];
|
|
1315
|
+
for (const a of acksNow)
|
|
1316
|
+
if (a.messageId === messageId)
|
|
1317
|
+
a.emoji = "Get";
|
|
1318
|
+
this.pendingAckMessages.set(chatId, acksNow);
|
|
1319
|
+
await this.processQueue(chatId);
|
|
1320
|
+
return;
|
|
1321
|
+
}
|
|
1290
1322
|
// Reconciliation may have finished the old owner during the steer RPC.
|
|
1291
1323
|
// Ensure this new, still-pending input is drained even when that owner
|
|
1292
1324
|
// exited through an error path. Already-consumed input is never replayed.
|
|
@@ -1437,13 +1469,36 @@ export class FeishuBot {
|
|
|
1437
1469
|
return existing;
|
|
1438
1470
|
}
|
|
1439
1471
|
const run = this.processQueueInner(chatId).finally(() => {
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1472
|
+
// Ownership token guard: only the CURRENT owner may release the slot. A
|
|
1473
|
+
// zombie owner that settles after forceExpireQueueOwner must not delete
|
|
1474
|
+
// the replacement owner's entry (that would open a concurrent-run window).
|
|
1475
|
+
if (this.queueRuns.get(chatId) === run) {
|
|
1476
|
+
this.queueRuns.delete(chatId);
|
|
1477
|
+
this.runFailureObservers.delete(chatId);
|
|
1478
|
+
this.admittedSteerAcks.delete(chatId);
|
|
1479
|
+
}
|
|
1443
1480
|
});
|
|
1444
1481
|
this.queueRuns.set(chatId, run);
|
|
1445
1482
|
return run;
|
|
1446
1483
|
}
|
|
1484
|
+
/**
|
|
1485
|
+
* Force-release a stuck queue owner after the gateway authoritatively
|
|
1486
|
+
* reported the session idle (no active run). The zombie owner's deep waits
|
|
1487
|
+
* can otherwise hold chat custody forever: completion events routed through
|
|
1488
|
+
* the proactive path are invisible to them. Steer-fallback triggers are
|
|
1489
|
+
* restored via the failure observers so their input drains normally.
|
|
1490
|
+
*/
|
|
1491
|
+
forceExpireQueueOwner(chatId, reason) {
|
|
1492
|
+
const owner = this.queueRuns.get(chatId);
|
|
1493
|
+
if (!owner)
|
|
1494
|
+
return false;
|
|
1495
|
+
console.warn(`[${this.config.name}] force-expiring zombie queue owner for ${chatId.slice(-8)}: ${reason}`);
|
|
1496
|
+
this.queueRuns.delete(chatId);
|
|
1497
|
+
this.fireRunFailureObservers(chatId);
|
|
1498
|
+
this.runFailureObservers.delete(chatId);
|
|
1499
|
+
this.admittedSteerAcks.delete(chatId);
|
|
1500
|
+
return true;
|
|
1501
|
+
}
|
|
1447
1502
|
async processQueueInner(chatId) {
|
|
1448
1503
|
while (true) {
|
|
1449
1504
|
// A fresh batch starts: steered inputs admitted into the PREVIOUS batch
|
|
@@ -178,7 +178,7 @@ export declare class OpenClawClient {
|
|
|
178
178
|
* the ambiguous public admission as a realtime-insertion success signal.
|
|
179
179
|
*/
|
|
180
180
|
steer(sessionKey: string, text: string, displayText?: string): Promise<{
|
|
181
|
-
status: "steered" | "unavailable";
|
|
181
|
+
status: "steered" | "unavailable" | "no_active_run";
|
|
182
182
|
runId?: string;
|
|
183
183
|
/** Cancel provisional transcript correlation when LMA falls back to a normal run. */
|
|
184
184
|
cancelPending?: () => void;
|
package/dist/openclaw-client.js
CHANGED
|
@@ -1628,6 +1628,10 @@ export class OpenClawClient {
|
|
|
1628
1628
|
console.warn(`[OpenClaw] lma.steer ${res.status} for ${key.slice(-8)}`);
|
|
1629
1629
|
}
|
|
1630
1630
|
cancelPending();
|
|
1631
|
+
// Pass the gateway's authoritative "no active run" verdict through: the
|
|
1632
|
+
// caller uses it to detect a zombie queue owner (2026-10-15 incident).
|
|
1633
|
+
if (res?.status === "no_active_run")
|
|
1634
|
+
return { status: "no_active_run" };
|
|
1631
1635
|
return { status: "unavailable" };
|
|
1632
1636
|
}
|
|
1633
1637
|
return { status: "steered", cancelPending };
|