openclaw-lark-multi-agent 1.8.17 → 1.8.18
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/message-store.d.ts +1 -0
- package/dist/message-store.js +4 -0
- package/dist/queued-presentation.js +36 -17
- package/docs/releases/1.8.18.md +23 -0
- package/package.json +3 -2
package/dist/message-store.d.ts
CHANGED
|
@@ -315,6 +315,7 @@ export declare class MessageStore {
|
|
|
315
315
|
*/
|
|
316
316
|
insert(msg: ChatMessage): number;
|
|
317
317
|
getMessageId(messageId: string): number | null;
|
|
318
|
+
getMessageByRowId(rowId: number): ChatMessage | null;
|
|
318
319
|
getMessageByMessageId(messageId: string): ChatMessage | null;
|
|
319
320
|
getLatestMessageId(chatId: string): number;
|
|
320
321
|
markMessageRecalled(messageId: string, chatId: string, recalledAt?: number, recallType?: string): void;
|
package/dist/message-store.js
CHANGED
|
@@ -1164,6 +1164,10 @@ export class MessageStore {
|
|
|
1164
1164
|
const row = this.db.prepare(`SELECT id FROM messages WHERE message_id = ?`).get(messageId);
|
|
1165
1165
|
return row?.id || this.db.prepare('SELECT message_row_id FROM platform_message_aliases WHERE message_id=?').get(messageId)?.message_row_id || null;
|
|
1166
1166
|
}
|
|
1167
|
+
getMessageByRowId(rowId) {
|
|
1168
|
+
const row = this.db.prepare('SELECT message_id FROM messages WHERE id = ?').get(rowId);
|
|
1169
|
+
return row ? this.getMessageByMessageId(row.message_id) : null;
|
|
1170
|
+
}
|
|
1167
1171
|
getMessageByMessageId(messageId) {
|
|
1168
1172
|
const row = this.db.prepare(`SELECT * FROM messages WHERE message_id = ?`).get(messageId);
|
|
1169
1173
|
if (!row)
|
|
@@ -428,51 +428,70 @@ export class QueuedPresentations {
|
|
|
428
428
|
const evidence = this.store.getDeliveredRunReply(this.bot, this.app, chat, runId, epoch);
|
|
429
429
|
if (!evidence)
|
|
430
430
|
return;
|
|
431
|
-
const
|
|
431
|
+
const witnesses = (row, preserveSealedPayload = false) => {
|
|
432
432
|
const retry = row.view?.state === "replied" && !!row.linkedReply && !row.linkedReply.rendered &&
|
|
433
433
|
row.create === "accepted" && !!row.cardId && !Object.keys(row.editEffects || {}).length;
|
|
434
|
-
|
|
434
|
+
const preserved = preserveSealedPayload && row.linkedReply?.rendered === 'reply_payload';
|
|
435
|
+
if ((!this.usable(row) && !retry && !preserved) || row.retired || row.stopEpoch !== epoch || row.linkedReply?.rendered && !preserved ||
|
|
435
436
|
evidence.createdAt < row.startedAt || (row.expectedInsertionRoot !== runId && row.runId !== runId))
|
|
436
|
-
return
|
|
437
|
-
const ordinary = row.runId
|
|
437
|
+
return;
|
|
438
|
+
const ordinary = row.runId && row.ownerId && row.batchId ? row.ordinaryTriggerIds || [] : [];
|
|
438
439
|
const extra = row.triggerIds.filter(id => !ordinary.includes(id));
|
|
439
|
-
|
|
440
|
+
if (!extra.length)
|
|
441
|
+
return;
|
|
442
|
+
const proofs = [];
|
|
443
|
+
for (const id of row.triggerIds) {
|
|
444
|
+
const input = this.store.getMessageByRowId(id);
|
|
440
445
|
const item = this.store.getInsertion(this.bot, chat, id);
|
|
441
|
-
const
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
+
const isOrdinary = ordinary.includes(id);
|
|
447
|
+
if (isOrdinary ? item && (item.state !== 'ordinary' || item.stopEpoch !== epoch) :
|
|
448
|
+
!row.enqueueAcceptedIds?.includes(id) || item?.state !== 'accepted' || item.stopEpoch !== epoch)
|
|
449
|
+
return;
|
|
450
|
+
if (!isOrdinary && !item.sessionId)
|
|
451
|
+
return;
|
|
452
|
+
const root = isOrdinary ? row.runId : item.expectedRunId;
|
|
453
|
+
const proof = root ? this.store.getDeliveredRunReply(this.bot, this.app, chat, root, epoch) : undefined;
|
|
454
|
+
if (!proof || input?.id !== id || input.chatId !== chat || input.senderType !== 'human' || input.timestamp > proof.createdAt ||
|
|
455
|
+
item && (item.messageId !== input.messageId || proof.sessionId && item.sessionId && item.sessionId !== proof.sessionId))
|
|
456
|
+
return;
|
|
457
|
+
proofs.push(proof);
|
|
458
|
+
}
|
|
459
|
+
return proofs;
|
|
446
460
|
};
|
|
447
461
|
const flights = [];
|
|
448
462
|
// There is no await in this staging loop: seal every currently eligible
|
|
449
463
|
// cohort before the first SDK effect can allow new ingress to interleave.
|
|
450
464
|
for (const snapshot of this.records(chat)) {
|
|
451
465
|
const row = this.latest(snapshot);
|
|
452
|
-
if (!row || !
|
|
466
|
+
if (!row || !witnesses(row) || this.replyFlights.has(row.id) || (row.linkedReply?.retryAfter || 0) > Date.now())
|
|
453
467
|
continue;
|
|
454
468
|
const entry = this.hydrate(row);
|
|
455
469
|
const current = this.latest(entry.record);
|
|
456
|
-
|
|
470
|
+
const proofs = current && witnesses(current);
|
|
471
|
+
if (!current || !proofs || current.lease !== entry.record.lease)
|
|
457
472
|
continue;
|
|
473
|
+
const latestProof = proofs.reduce((a, b) => a.deliveredAt >= b.deliveredAt ? a : b);
|
|
474
|
+
const proofIds = [...new Set(proofs.map(proof => proof.outboxId))].sort((a, b) => a - b);
|
|
458
475
|
const ids = JSON.stringify(current.triggerIds);
|
|
459
|
-
const preservePayload = !!current.cardId &&
|
|
476
|
+
const preservePayload = !!current.cardId && proofs.some(proof => proof.platformMessageIds.includes(current.cardId));
|
|
460
477
|
// Desired persistence may itself have failed while the controller became
|
|
461
478
|
// terminal. A sealed, unrendered, clear-effect obligation permits only
|
|
462
479
|
// the controller's own no-flight/no-unknown retry, not business replay.
|
|
463
480
|
const retry = !!current.linkedReply && !current.linkedReply.rendered && !Object.keys(current.editEffects || {}).length;
|
|
464
481
|
const attempts = (current.linkedReply?.attempts || 0) + 1;
|
|
465
|
-
this.save({ ...current, linkedReply: { runId, outboxId:
|
|
482
|
+
this.save({ ...current, linkedReply: { runId: latestProof.runId, outboxId: latestProof.outboxId, deliveredAt: latestProof.deliveredAt,
|
|
483
|
+
relatedOutboxIds: proofIds, attempts,
|
|
466
484
|
retryAfter: Date.now() + Math.min(15 * 60_000, 60_000 * 2 ** Math.min(attempts - 1, 4)),
|
|
467
485
|
...(preservePayload ? { rendered: "reply_payload" } : {}) } });
|
|
468
486
|
const flight = Promise.resolve().then(async () => {
|
|
469
487
|
const fresh = this.latest(entry.record);
|
|
470
|
-
const
|
|
488
|
+
const freshProofs = fresh && witnesses(fresh, preservePayload);
|
|
471
489
|
if (this.disposed || !fresh || fresh.retired || fresh.lease !== entry.record.lease ||
|
|
472
490
|
fresh.stopEpoch !== this.store.getStopGeneration(this.bot, chat) || JSON.stringify(fresh.triggerIds) !== ids ||
|
|
473
|
-
fresh.linkedReply?.outboxId !==
|
|
491
|
+
fresh.linkedReply?.outboxId !== latestProof.outboxId || !freshProofs ||
|
|
492
|
+
JSON.stringify([...new Set(freshProofs.map(proof => proof.outboxId))].sort((a, b) => a - b)) !== JSON.stringify(proofIds))
|
|
474
493
|
return;
|
|
475
|
-
await entry.live.linkedReplyDelivered(
|
|
494
|
+
await entry.live.linkedReplyDelivered(latestProof.deliveredAt, preservePayload, retry);
|
|
476
495
|
}).catch(() => {
|
|
477
496
|
// Own rejection immediately: a later synchronous cohort read can fail
|
|
478
497
|
// before this method reaches allSettled. UI outages must not crash LMA.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# 1.8.18 — Independently witness every reply in a historical mixed card
|
|
2
|
+
|
|
3
|
+
Extends 1.8.17's presentation-only fix for a live race observed during validation:
|
|
4
|
+
1.8.16 had already reused an old insertion's waiting card for a newer ordinary
|
|
5
|
+
run. Closing it from only the old run's reply would be incorrect; leaving it
|
|
6
|
+
waiting after both separate replies were delivered would also be misleading.
|
|
7
|
+
|
|
8
|
+
The card now closes only when **each member's own associated run** has a complete
|
|
9
|
+
original-Bot/application/chat/epoch platform reply receipt that postdates that
|
|
10
|
+
member. The newer ordinary reply cannot substitute for the older insertion's
|
|
11
|
+
reply, or vice versa. Elapsed time uses the latest verified delivery. Persist the
|
|
12
|
+
exact related outbox IDs; revalidate all proofs and membership before the effect.
|
|
13
|
+
If any verified reply already occupies the card, retain its business content
|
|
14
|
+
without a compact replacement. Pending/unknown members still prevent closure.
|
|
15
|
+
|
|
16
|
+
No input commitment/consumption, Get/DONE, model-sync, admission, business retry,
|
|
17
|
+
Core/plugin/config or schema changes. Bridge activation only; native dependencies
|
|
18
|
+
are unchanged. Existing unknown-effect fencing and UI-only zero-effect retries
|
|
19
|
+
remain intact. 1.8.17 tags/packages are not replaced; this is a new immutable patch.
|
|
20
|
+
|
|
21
|
+
Validation: bridge/plugins build, public SDK contract, **66 files / 1795 offline
|
|
22
|
+
tests**, including 32 linked-card controls (4 new historical mixed-card cases).
|
|
23
|
+
Live platform-card readback remains a separate deployment gate.
|
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.18",
|
|
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": {
|
|
@@ -78,7 +78,8 @@
|
|
|
78
78
|
"docs/releases/1.8.11.md",
|
|
79
79
|
"docs/releases/1.8.15.md",
|
|
80
80
|
"docs/releases/1.8.16.md",
|
|
81
|
-
"docs/releases/1.8.17.md"
|
|
81
|
+
"docs/releases/1.8.17.md",
|
|
82
|
+
"docs/releases/1.8.18.md"
|
|
82
83
|
],
|
|
83
84
|
"engines": {
|
|
84
85
|
"node": ">=22"
|