openclaw-lark-multi-agent 1.8.7 → 1.8.9
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/message-store.d.ts +3 -0
- package/dist/message-store.js +7 -0
- package/dist/openclaw-client.js +41 -32
- package/package.json +1 -1
package/dist/feishu-bot.js
CHANGED
|
@@ -1458,14 +1458,15 @@ export class FeishuBot {
|
|
|
1458
1458
|
// 息都发个这个"). Stay silent until it persists through several
|
|
1459
1459
|
// re-checks (~45s), then notify exactly once.
|
|
1460
1460
|
const unknownChecks = state === 'unknown' ? (Number(item.unknownChecks) || 0) + 1 : 0;
|
|
1461
|
-
const
|
|
1462
|
-
const record = { ...item, attempted: valid ? (receipt.attempted ?? true) : true, sessionId: item.sessionId, reason: receipt.reason, unknownChecks, unknownNotified };
|
|
1461
|
+
const record = { ...item, attempted: valid ? (receipt.attempted ?? true) : true, sessionId: item.sessionId, reason: receipt.reason, unknownChecks };
|
|
1463
1462
|
this.store.saveInsertion(this.config.name, chatId, item.rowId, item.insertionId, state, record, Date.now() + (state === 'unknown' ? 15_000 : 5000));
|
|
1464
1463
|
if (state === 'ordinary') {
|
|
1464
|
+
this.store.deletePendingInsertionNotices(this.config.name, chatId, item.insertionId);
|
|
1465
1465
|
void this.enqueueAndDispatchDelivery(chatId, 'insertion_status', `insertion:${item.insertionId}:ordinary`, 'ℹ️ 原 run 已明确结束,且这条消息确定未被插入;现已交回普通队列,只执行一次。', [], item.messageId, `insertion:${item.insertionId}:ordinary`).catch(() => { });
|
|
1466
1466
|
void this.processQueue(chatId).catch(() => { });
|
|
1467
1467
|
}
|
|
1468
1468
|
else if (state === 'accepted') {
|
|
1469
|
+
this.store.deletePendingInsertionNotices(this.config.name, chatId, item.insertionId);
|
|
1469
1470
|
this.store.clearPendingTrigger(this.config.name, chatId, item.rowId);
|
|
1470
1471
|
// Durable acceptance drives Get, including after bridge restart when
|
|
1471
1472
|
// its ephemeral pendingAckMessages array no longer exists.
|
|
@@ -1486,8 +1487,17 @@ export class FeishuBot {
|
|
|
1486
1487
|
if (item.state !== state)
|
|
1487
1488
|
void this.enqueueAndDispatchDelivery(chatId, 'insertion_status', `insertion:${item.insertionId}:${state}`, `ℹ️ 这条插入消息的状态为 ${state},仍保留本地记录,未确认接收前不会显示 Get,也不会自动重复执行。${receipt.reason || ''}`, [], item.messageId, `insertion:${item.insertionId}:${state}`).catch(() => { });
|
|
1488
1489
|
}
|
|
1489
|
-
else if (state === 'unknown' &&
|
|
1490
|
-
|
|
1490
|
+
else if (state === 'unknown' && unknownChecks >= 4 && !item.unknownNotified) {
|
|
1491
|
+
// ~45s (attempt + 3 re-checks). Mark unknownNotified ONLY after the
|
|
1492
|
+
// outbox insert succeeds, so a failed enqueue retries next check
|
|
1493
|
+
// instead of silencing the notice forever.
|
|
1494
|
+
void this.enqueueAndDispatchDelivery(chatId, 'insertion_status', `insertion:${item.insertionId}:unknown-prolonged`, `ℹ️ 这条插入消息约 45 秒仍未确认接收:本地记录保留中,不会自动重复执行;确认后会自动显示 Get。`, [], item.messageId, `insertion:${item.insertionId}:unknown-prolonged`)
|
|
1495
|
+
.then(() => {
|
|
1496
|
+
const cur = this.store.getInsertion(this.config.name, chatId, item.rowId);
|
|
1497
|
+
const curRecord = (cur && cur.record) || record;
|
|
1498
|
+
this.store.saveInsertion(this.config.name, chatId, item.rowId, item.insertionId, (cur && cur.state) || state, { ...curRecord, unknownNotified: true }, Date.now() + 15_000);
|
|
1499
|
+
})
|
|
1500
|
+
.catch(() => { });
|
|
1491
1501
|
}
|
|
1492
1502
|
}
|
|
1493
1503
|
}
|
package/dist/message-store.d.ts
CHANGED
|
@@ -131,6 +131,9 @@ export declare class MessageStore {
|
|
|
131
131
|
finishRunObservation(sessionKey: string, runId: string, state?: string): void;
|
|
132
132
|
listRunObservations(sessionKey?: string, due?: number, limit?: number): any[];
|
|
133
133
|
saveInsertion(bot: string, chat: string, row: number, id: string, state: string, record: object, next?: number): void;
|
|
134
|
+
/** Purge still-pending insertion status notices once the insertion resolves
|
|
135
|
+
* (accepted/ordinary): a stale "unknown" notice must not fire after Get. */
|
|
136
|
+
deletePendingInsertionNotices(bot: string, chat: string, insertionId: string): number;
|
|
134
137
|
claimInsertionAttempt(bot: string, chat: string, row: number): boolean;
|
|
135
138
|
getInsertion(bot: string, chat: string, row: number): any;
|
|
136
139
|
listInsertions(bot: string, chat?: string): any[];
|
package/dist/message-store.js
CHANGED
|
@@ -159,6 +159,13 @@ export class MessageStore {
|
|
|
159
159
|
ON CONFLICT(bot_name,chat_id,message_row_id) DO UPDATE SET state=excluded.state,record_json=excluded.record_json,next_check_at=excluded.next_check_at
|
|
160
160
|
WHERE insertion_custody.state NOT IN ('stopped','committed')`).run(bot, chat, row, id, JSON.stringify(record), state, next);
|
|
161
161
|
}
|
|
162
|
+
/** Purge still-pending insertion status notices once the insertion resolves
|
|
163
|
+
* (accepted/ordinary): a stale "unknown" notice must not fire after Get. */
|
|
164
|
+
deletePendingInsertionNotices(bot, chat, insertionId) {
|
|
165
|
+
const r = this.db.prepare(`DELETE FROM delivery_outbox WHERE bot_name=? AND chat_id=? AND delivery_key LIKE ? AND status='pending'`)
|
|
166
|
+
.run(bot, chat, `insertion:${insertionId}:%`);
|
|
167
|
+
return r.changes;
|
|
168
|
+
}
|
|
162
169
|
claimInsertionAttempt(bot, chat, row) {
|
|
163
170
|
return this.db.prepare(`UPDATE insertion_custody SET state='dispatching',record_json=json_set(record_json,'$.attempted',json('true'))
|
|
164
171
|
WHERE bot_name=? AND chat_id=? AND message_row_id=? AND state='pending'`).run(bot, chat, row).changes === 1;
|
package/dist/openclaw-client.js
CHANGED
|
@@ -900,11 +900,12 @@ export class OpenClawClient {
|
|
|
900
900
|
let lifecycleEndTimer = null;
|
|
901
901
|
let replayInvalidTimer = null;
|
|
902
902
|
let genericChatErrorTimer = null;
|
|
903
|
-
// A
|
|
904
|
-
//
|
|
905
|
-
//
|
|
906
|
-
|
|
907
|
-
|
|
903
|
+
// A terminating message-tool send schedules ONE deferred exact-run
|
|
904
|
+
// reconciliation. The timer carries no settlement or delivery authority
|
|
905
|
+
// of its own; checkRunState still requires the gateway to confirm the
|
|
906
|
+
// root's recorded terminal state (all tools settled, no error, no async
|
|
907
|
+
// pending) before anything resolves.
|
|
908
|
+
let terminatingReconcileTimer = null;
|
|
908
909
|
let pendingRuntimeFailureText = "";
|
|
909
910
|
let lastActivitySummary = "";
|
|
910
911
|
let lastActivityAt = 0;
|
|
@@ -1179,8 +1180,8 @@ export class OpenClawClient {
|
|
|
1179
1180
|
clearTimeout(replayInvalidTimer);
|
|
1180
1181
|
if (genericChatErrorTimer)
|
|
1181
1182
|
clearTimeout(genericChatErrorTimer);
|
|
1182
|
-
if (
|
|
1183
|
-
clearTimeout(
|
|
1183
|
+
if (terminatingReconcileTimer)
|
|
1184
|
+
clearTimeout(terminatingReconcileTimer);
|
|
1184
1185
|
resolve(finalText);
|
|
1185
1186
|
};
|
|
1186
1187
|
const collectEvents = () => {
|
|
@@ -1317,39 +1318,45 @@ export class OpenClawClient {
|
|
|
1317
1318
|
if (hasEffectiveActivity(ev))
|
|
1318
1319
|
resetIdleTimer();
|
|
1319
1320
|
rememberActivity(ev);
|
|
1320
|
-
// Terminating channel-send detection
|
|
1321
|
-
//
|
|
1322
|
-
//
|
|
1323
|
-
//
|
|
1324
|
-
//
|
|
1325
|
-
//
|
|
1321
|
+
// Terminating channel-send detection (2026-10-16 frozen-card
|
|
1322
|
+
// incident, remediated per independent review): a message-tool
|
|
1323
|
+
// result carrying a top-level terminate=true is a batch-control
|
|
1324
|
+
// hint — NOT a delivery receipt and NOT settlement authority for
|
|
1325
|
+
// this collector. It only nudges the exact-run reconciliation:
|
|
1326
|
+
// checkRunState requires a recorded terminal time, all tools
|
|
1327
|
+
// settled, no error and no async pending before resolving; a run
|
|
1328
|
+
// that is still executing keeps waiting normally. No tool input is
|
|
1329
|
+
// ever re-delivered to the source chat by this path.
|
|
1326
1330
|
{
|
|
1327
1331
|
const tdata = ev.data || {};
|
|
1328
1332
|
if (ev.stream === "tool" || (ev.stream === "item" && tdata.kind === "tool")) {
|
|
1329
1333
|
const toolName = String(tdata.name || tdata.toolName || "");
|
|
1330
1334
|
const tphase = tdata.phase === "result" ? (tdata.isError || tdata.error ? "error" : "end") : tdata.phase;
|
|
1331
|
-
if (toolName === "message" && tphase === "
|
|
1332
|
-
const
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1335
|
+
if (toolName === "message" && (tphase === "end" || tphase === "error")) {
|
|
1336
|
+
const rawOut = tdata.output ?? tdata.result ?? tdata.error ?? null;
|
|
1337
|
+
let terminating = false;
|
|
1338
|
+
try {
|
|
1339
|
+
if (rawOut && typeof rawOut === "object")
|
|
1340
|
+
terminating = rawOut.terminate === true;
|
|
1341
|
+
else if (typeof rawOut === "string") {
|
|
1342
|
+
const trimmed = rawOut.trim();
|
|
1343
|
+
if (trimmed.startsWith("{")) {
|
|
1344
|
+
const parsed = JSON.parse(trimmed);
|
|
1345
|
+
terminating = parsed && typeof parsed === "object" && parsed.terminate === true;
|
|
1346
|
+
}
|
|
1347
|
+
else
|
|
1348
|
+
terminating = /^terminate\s*=\s*true$/i.test(trimmed);
|
|
1338
1349
|
}
|
|
1339
|
-
catch { /* non-JSON tool input */ }
|
|
1340
1350
|
}
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
terminatingSendTimer = setTimeout(() => {
|
|
1346
|
-
terminatingSendTimer = null;
|
|
1351
|
+
catch { /* unparseable output is not evidence */ }
|
|
1352
|
+
if (terminating && !terminatingReconcileTimer) {
|
|
1353
|
+
terminatingReconcileTimer = setTimeout(() => {
|
|
1354
|
+
terminatingReconcileTimer = null;
|
|
1347
1355
|
if (observationClosed)
|
|
1348
1356
|
return;
|
|
1349
|
-
|
|
1350
|
-
finish(best || "NO_REPLY");
|
|
1357
|
+
void checkRunState(() => true, true);
|
|
1351
1358
|
}, 10_000);
|
|
1352
|
-
|
|
1359
|
+
terminatingReconcileTimer.unref?.();
|
|
1353
1360
|
}
|
|
1354
1361
|
}
|
|
1355
1362
|
}
|
|
@@ -1613,6 +1620,8 @@ export class OpenClawClient {
|
|
|
1613
1620
|
clearTimeout(lifecycleEndTimer);
|
|
1614
1621
|
if (genericChatErrorTimer)
|
|
1615
1622
|
clearTimeout(genericChatErrorTimer);
|
|
1623
|
+
if (terminatingReconcileTimer)
|
|
1624
|
+
clearTimeout(terminatingReconcileTimer);
|
|
1616
1625
|
// Abort the (possibly still-running) backgrounded run before we
|
|
1617
1626
|
// reject, so an auto-retry probe does not race a zombie run on the
|
|
1618
1627
|
// same session. Idempotent/best-effort.
|
|
@@ -1669,8 +1678,8 @@ export class OpenClawClient {
|
|
|
1669
1678
|
clearTimeout(replayInvalidTimer);
|
|
1670
1679
|
if (genericChatErrorTimer)
|
|
1671
1680
|
clearTimeout(genericChatErrorTimer);
|
|
1672
|
-
if (
|
|
1673
|
-
clearTimeout(
|
|
1681
|
+
if (terminatingReconcileTimer)
|
|
1682
|
+
clearTimeout(terminatingReconcileTimer);
|
|
1674
1683
|
if (observationKey && this.runStateChecks.get(observationKey) === checkAfterNoActiveRun)
|
|
1675
1684
|
this.runStateChecks.delete(observationKey);
|
|
1676
1685
|
};
|