openclaw-lark-multi-agent 1.8.8 → 1.8.10
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/cli.js +20 -0
- package/dist/feishu-bot.d.ts +8 -0
- package/dist/feishu-bot.js +269 -31
- package/dist/message-store.d.ts +16 -0
- package/dist/message-store.js +68 -1
- package/dist/openclaw-client.d.ts +32 -6
- package/dist/openclaw-client.js +224 -60
- package/dist/session-status.d.ts +2 -0
- package/dist/session-status.js +2 -0
- package/docs/releases/1.8.10.md +31 -0
- package/package.json +4 -3
- package/scripts/update-lma-core.sh +3 -2
package/dist/message-store.js
CHANGED
|
@@ -28,6 +28,7 @@ export class MessageStore {
|
|
|
28
28
|
CREATE TABLE IF NOT EXISTS reaction_custody(bot_name TEXT NOT NULL,chat_id TEXT NOT NULL,row_id INTEGER NOT NULL,record_json TEXT NOT NULL,
|
|
29
29
|
PRIMARY KEY(bot_name,chat_id,row_id));
|
|
30
30
|
CREATE TABLE IF NOT EXISTS run_delivery_targets(bot_name TEXT NOT NULL,chat_id TEXT NOT NULL,run_id TEXT NOT NULL,record_json TEXT NOT NULL,PRIMARY KEY(bot_name,chat_id,run_id));
|
|
31
|
+
CREATE TABLE IF NOT EXISTS dispatch_custody(bot_name TEXT NOT NULL,chat_id TEXT NOT NULL,record_json TEXT NOT NULL,PRIMARY KEY(bot_name,chat_id));
|
|
31
32
|
CREATE TABLE IF NOT EXISTS admission_custody(session_key TEXT NOT NULL,submission_id TEXT PRIMARY KEY,state TEXT NOT NULL,record_json TEXT NOT NULL);
|
|
32
33
|
CREATE TABLE IF NOT EXISTS run_observations (
|
|
33
34
|
session_key TEXT NOT NULL, run_id TEXT NOT NULL, record_json TEXT NOT NULL,
|
|
@@ -81,6 +82,38 @@ export class MessageStore {
|
|
|
81
82
|
const row = this.db.prepare('SELECT record_json FROM run_delivery_targets WHERE bot_name=? AND chat_id=? AND run_id=?').get(bot, chat, runId);
|
|
82
83
|
return row ? JSON.parse(row.record_json) : null;
|
|
83
84
|
}
|
|
85
|
+
/** Scheduling custody is independent of exact-run result custody. */
|
|
86
|
+
saveDispatchCustody(bot, chat, record) {
|
|
87
|
+
this.db.prepare('INSERT OR REPLACE INTO dispatch_custody VALUES(?,?,?)').run(bot, chat, JSON.stringify(record));
|
|
88
|
+
}
|
|
89
|
+
getDispatchCustody(bot, chat) {
|
|
90
|
+
const row = this.db.prepare('SELECT record_json FROM dispatch_custody WHERE bot_name=? AND chat_id=?').get(bot, chat);
|
|
91
|
+
const item = row ? JSON.parse(row.record_json) : null;
|
|
92
|
+
return item?.stopEpoch === this.getStopGeneration(bot, chat) ? item : null;
|
|
93
|
+
}
|
|
94
|
+
clearDispatchCustody(bot, chat, ownerId, batchId) {
|
|
95
|
+
return this.db.prepare(`DELETE FROM dispatch_custody WHERE bot_name=? AND chat_id=?
|
|
96
|
+
AND json_extract(record_json,'$.id')=? AND json_extract(record_json,'$.batchId')=?`).run(bot, chat, ownerId, batchId).changes === 1;
|
|
97
|
+
}
|
|
98
|
+
/** Atomic release of proven never-attempted reservations only. Unknown and
|
|
99
|
+
* accepted inputs retain their exact target and never enter ordinary drain. */
|
|
100
|
+
releaseDispatchCustody(bot, chat, ownerId, batchId) {
|
|
101
|
+
return this.db.transaction(() => {
|
|
102
|
+
const custody = this.getDispatchCustody(bot, chat);
|
|
103
|
+
if (!custody || !this.clearDispatchCustody(bot, chat, ownerId, batchId))
|
|
104
|
+
return false;
|
|
105
|
+
this.db.prepare(`UPDATE run_delivery_targets SET record_json=json_set(record_json,'$.dispatchReleased',json('true'),
|
|
106
|
+
'$.dispatchOwnerId',?,'$.dispatchBatchId',?,'$.sessionId',?) WHERE bot_name=? AND chat_id=? AND run_id=?`)
|
|
107
|
+
.run(ownerId, batchId, custody.sessionId, bot, chat, custody.runId);
|
|
108
|
+
this.db.prepare(`UPDATE insertion_custody SET state='ordinary',next_check_at=0,
|
|
109
|
+
record_json=json_set(record_json,'$.reason','current session confirmed schedulable; no backend attempt')
|
|
110
|
+
WHERE bot_name=? AND chat_id=? AND state='pending'
|
|
111
|
+
AND COALESCE(json_extract(record_json,'$.attempted'),0)=0
|
|
112
|
+
AND json_extract(record_json,'$.ownerId')=? AND json_extract(record_json,'$.batchId')=?
|
|
113
|
+
AND json_extract(record_json,'$.stopEpoch')=?`).run(bot, chat, ownerId, batchId, this.getStopGeneration(bot, chat));
|
|
114
|
+
return true;
|
|
115
|
+
})();
|
|
116
|
+
}
|
|
84
117
|
/** Reserved is persisted before the send-attempt callback; dispatching is
|
|
85
118
|
* persisted before the RPC. Only reserved + unchanged stop generation proves
|
|
86
119
|
* no business effect and may be restored on startup. Unknown never replays. */
|
|
@@ -116,6 +149,9 @@ export class MessageStore {
|
|
|
116
149
|
return !!this.db.prepare("SELECT 1 FROM run_observations WHERE session_key=? AND run_id=? AND state='stopped'").get(sessionKey, runId);
|
|
117
150
|
}
|
|
118
151
|
settleRunReactionIntents(bot, chat, runId) {
|
|
152
|
+
this.db.prepare(`UPDATE insertion_custody SET state='committed' WHERE bot_name=? AND chat_id=? AND state='accepted'
|
|
153
|
+
AND json_extract(record_json,'$.expectedRunId')=? AND json_extract(record_json,'$.stopEpoch')=?`)
|
|
154
|
+
.run(bot, chat, runId, this.getStopGeneration(bot, chat));
|
|
119
155
|
this.db.prepare(`UPDATE reaction_custody SET record_json=json_set(record_json,'$.desired','DONE')
|
|
120
156
|
WHERE bot_name=? AND chat_id=? AND json_extract(record_json,'$.runId')=?
|
|
121
157
|
AND json_extract(record_json,'$.desired') IN ('Get','Typing')
|
|
@@ -127,9 +163,16 @@ export class MessageStore {
|
|
|
127
163
|
}
|
|
128
164
|
saveRunObservation(sessionKey, runId, record, nextCheckAt = Date.now()) {
|
|
129
165
|
this.db.prepare(`INSERT INTO run_observations VALUES(?,?,?,'observing',?)
|
|
130
|
-
ON CONFLICT(session_key,run_id) DO UPDATE SET record_json=excluded.record_json,next_check_at=excluded.next_check_at
|
|
166
|
+
ON CONFLICT(session_key,run_id) DO UPDATE SET record_json=json_patch(run_observations.record_json,excluded.record_json),next_check_at=excluded.next_check_at
|
|
131
167
|
WHERE run_observations.state='observing'`).run(sessionKey, runId, JSON.stringify(record), nextCheckAt);
|
|
132
168
|
}
|
|
169
|
+
hasYieldedObservation(sessionKey) {
|
|
170
|
+
return !!this.db.prepare(`SELECT 1 FROM run_observations WHERE session_key=? AND json_extract(record_json,'$.yielded')=1 LIMIT 1`).get(sessionKey);
|
|
171
|
+
}
|
|
172
|
+
getRunObservation(sessionKey, runId) {
|
|
173
|
+
const row = this.db.prepare('SELECT record_json,state FROM run_observations WHERE session_key=? AND run_id=?').get(sessionKey, runId);
|
|
174
|
+
return row ? { ...JSON.parse(row.record_json), state: row.state } : null;
|
|
175
|
+
}
|
|
133
176
|
runObservationState(sessionKey, runId) {
|
|
134
177
|
return this.db.prepare('SELECT state FROM run_observations WHERE session_key=? AND run_id=?').get(sessionKey, runId)?.state;
|
|
135
178
|
}
|
|
@@ -159,6 +202,26 @@ export class MessageStore {
|
|
|
159
202
|
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
203
|
WHERE insertion_custody.state NOT IN ('stopped','committed')`).run(bot, chat, row, id, JSON.stringify(record), state, next);
|
|
161
204
|
}
|
|
205
|
+
/** Purge still-pending insertion status notices once the insertion resolves
|
|
206
|
+
* (accepted/ordinary): a stale "unknown" notice must not fire after Get. */
|
|
207
|
+
deletePendingInsertionNotices(bot, chat, insertionId) {
|
|
208
|
+
const r = this.db.prepare(`DELETE FROM delivery_outbox WHERE bot_name=? AND chat_id=? AND delivery_key LIKE ? AND status='pending'`)
|
|
209
|
+
.run(bot, chat, `insertion:${insertionId}:%`);
|
|
210
|
+
return r.changes;
|
|
211
|
+
}
|
|
212
|
+
/** Reminder intent and its outbox row commit together. Never write a stale
|
|
213
|
+
* reconciliation snapshot over a later accepted/stopped insertion. */
|
|
214
|
+
queueInsertionUnknownNotice(bot, chat, row, delivery) {
|
|
215
|
+
return this.db.transaction(() => {
|
|
216
|
+
const current = this.getInsertion(bot, chat, row);
|
|
217
|
+
if (current?.state !== 'unknown' || current.stopEpoch !== this.getStopGeneration(bot, chat) || current.unknownNotified ||
|
|
218
|
+
!Number.isFinite(current.unknownSince) || Date.now() - current.unknownSince < 45_000)
|
|
219
|
+
return false;
|
|
220
|
+
this.enqueueDelivery(delivery);
|
|
221
|
+
this.saveInsertion(bot, chat, row, current.insertionId, 'unknown', { ...current, unknownNotified: true }, Date.now() + 15_000);
|
|
222
|
+
return true;
|
|
223
|
+
})();
|
|
224
|
+
}
|
|
162
225
|
claimInsertionAttempt(bot, chat, row) {
|
|
163
226
|
return this.db.prepare(`UPDATE insertion_custody SET state='dispatching',record_json=json_set(record_json,'$.attempted',json('true'))
|
|
164
227
|
WHERE bot_name=? AND chat_id=? AND message_row_id=? AND state='pending'`).run(bot, chat, row).changes === 1;
|
|
@@ -176,6 +239,10 @@ export class MessageStore {
|
|
|
176
239
|
const item = this.getInsertion(bot, chat, row);
|
|
177
240
|
return !!item && !['ordinary', 'stopped'].includes(item.state);
|
|
178
241
|
}
|
|
242
|
+
stopInsertionAtEpoch(bot, chat, row, epoch) {
|
|
243
|
+
this.db.prepare(`UPDATE insertion_custody SET state='stopped' WHERE bot_name=? AND chat_id=? AND message_row_id=?
|
|
244
|
+
AND json_extract(record_json,'$.stopEpoch')=? AND state NOT IN ('committed','ordinary')`).run(bot, chat, row, epoch);
|
|
245
|
+
}
|
|
179
246
|
stopInsertions(bot, chat) {
|
|
180
247
|
this.db.prepare("UPDATE insertion_custody SET state='stopped' WHERE bot_name=? AND chat_id=? AND state NOT IN ('committed','ordinary')").run(bot, chat);
|
|
181
248
|
}
|
|
@@ -155,8 +155,8 @@ export declare class OpenClawClient {
|
|
|
155
155
|
* Collect agent reply by polling accumulated events.
|
|
156
156
|
* Matches by initial runId OR sessionKey to handle multi-turn tool calling
|
|
157
157
|
* where OpenClaw creates new runIds for each tool-call round.
|
|
158
|
-
* Existing event/anchor matching is retained.
|
|
159
|
-
*
|
|
158
|
+
* Existing event/anchor matching is retained. Foreground effective silence
|
|
159
|
+
* triggers read-only reconciliation, never a task-lifetime cutoff or draft final.
|
|
160
160
|
* Explicit terminal/error/disconnect paths keep their existing behavior.
|
|
161
161
|
*/
|
|
162
162
|
private collectReply;
|
|
@@ -192,7 +192,7 @@ export declare class OpenClawClient {
|
|
|
192
192
|
* expectedSessionId, so describe first, then patch archived=false.
|
|
193
193
|
*/
|
|
194
194
|
unarchiveSession(key: string): Promise<boolean>;
|
|
195
|
-
getSessionStatus(key: string): Promise<any>;
|
|
195
|
+
getSessionStatus(key: string, timeoutMs?: number): Promise<any>;
|
|
196
196
|
injectAssistantMessage(params: {
|
|
197
197
|
sessionKey: string;
|
|
198
198
|
message: string;
|
|
@@ -203,6 +203,7 @@ export declare class OpenClawClient {
|
|
|
203
203
|
* missing/legacy plugin observations stay pending without blind mutation. */
|
|
204
204
|
exactRunSettled(runId: string): Promise<boolean>;
|
|
205
205
|
cancelSteerSession(sessionKey: string, stopEpoch: number): Promise<void>;
|
|
206
|
+
private readinessCause;
|
|
206
207
|
steer(sessionKey: string, text: string, displayText?: string, identity?: {
|
|
207
208
|
insertionId: string;
|
|
208
209
|
expectedRunId: string;
|
|
@@ -251,9 +252,19 @@ export declare class OpenClawClient {
|
|
|
251
252
|
* Used when queue custody is revoked or a new chat.send takes over, so a
|
|
252
253
|
* stale poller cannot steal the replacement batch's frames (2026-10-16). */
|
|
253
254
|
revokeSessionCollectors(sessionKey: string, reason: string): number;
|
|
255
|
+
waitForSessionStop(sessionKey: string): Promise<void>;
|
|
254
256
|
getSessionRuntimeStatus(sessionKey: string): Promise<SessionRuntimeStatus>;
|
|
257
|
+
/** Strong public current-session activity, independent of expired root
|
|
258
|
+
* terminal caches. Unknown/missing fields never authorize scheduling. */
|
|
259
|
+
getSessionDispatchStatus(sessionKey: string): Promise<{
|
|
260
|
+
idleConfirmed: boolean;
|
|
261
|
+
sessionId?: string;
|
|
262
|
+
archived?: boolean;
|
|
263
|
+
activeRunId?: string;
|
|
264
|
+
busyConfirmed?: boolean;
|
|
265
|
+
}>;
|
|
255
266
|
private classifyWaitTimeout;
|
|
256
|
-
getSessionInfo(sessionKey: string): Promise<any>;
|
|
267
|
+
getSessionInfo(sessionKey: string, timeoutMs?: number): Promise<any>;
|
|
257
268
|
/**
|
|
258
269
|
* Ensure session is using the expected model. If not, patch it back.
|
|
259
270
|
* Returns true if a correction was made.
|
|
@@ -283,6 +294,11 @@ export declare class OpenClawClient {
|
|
|
283
294
|
private trackChatEventSession;
|
|
284
295
|
inspectRunError(provenance: ErrorProvenance): Promise<import("./terminal-errors.js").ErrorVerdict>;
|
|
285
296
|
private extractTextFromChatMessage;
|
|
297
|
+
private continuationChecks;
|
|
298
|
+
/** A child/requester continuation is not automatically its parent's final.
|
|
299
|
+
* Public task parentage selects the original root; only that exact root's
|
|
300
|
+
* committed terminalReply may enter its old trigger/outbox. */
|
|
301
|
+
private handoffLinkedContinuation;
|
|
286
302
|
private emitProactiveForSession;
|
|
287
303
|
private suppressSessionKeys;
|
|
288
304
|
private releaseSuppressedSessionKeysAfter;
|
|
@@ -415,9 +431,14 @@ export declare class OpenClawClient {
|
|
|
415
431
|
/** Called after chat.send RPC succeeds and OpenClaw has accepted the user message. */
|
|
416
432
|
onSubmitted?: (runId: string) => void | Promise<void>;
|
|
417
433
|
/** Identifies collection target; not an acceptance receipt. */
|
|
418
|
-
onRunIdentified?: (runId: string
|
|
434
|
+
onRunIdentified?: (runId: string, identity?: {
|
|
435
|
+
sessionId?: string;
|
|
436
|
+
}) => void | Promise<void>;
|
|
419
437
|
onProgress?: (event: ProgressEvent) => void | Promise<void>;
|
|
420
438
|
onWaitPaused?: (notice: SessionWaitPaused) => void | Promise<void>;
|
|
439
|
+
/** Synchronous durable scheduling handoff; never a task settlement. */
|
|
440
|
+
onYieldHandoff?: (runId: string) => void;
|
|
441
|
+
onCollectionStarted?: (runId: string) => void;
|
|
421
442
|
}): Promise<string>;
|
|
422
443
|
private shouldInjectBridgeAttachmentHint;
|
|
423
444
|
private bridgeAttachmentHint;
|
|
@@ -452,9 +473,14 @@ export declare class OpenClawClient {
|
|
|
452
473
|
/** Called after chat.send RPC succeeds and OpenClaw has accepted the combined message. */
|
|
453
474
|
onSubmitted?: (runId: string) => void | Promise<void>;
|
|
454
475
|
/** Identifies collection target; not an acceptance receipt. */
|
|
455
|
-
onRunIdentified?: (runId: string
|
|
476
|
+
onRunIdentified?: (runId: string, identity?: {
|
|
477
|
+
sessionId?: string;
|
|
478
|
+
}) => void | Promise<void>;
|
|
456
479
|
onProgress?: (event: ProgressEvent) => void | Promise<void>;
|
|
457
480
|
onWaitPaused?: (notice: SessionWaitPaused) => void | Promise<void>;
|
|
481
|
+
/** Synchronous durable scheduling handoff; never a task settlement. */
|
|
482
|
+
onYieldHandoff?: (runId: string) => void;
|
|
483
|
+
onCollectionStarted?: (runId: string) => void;
|
|
458
484
|
}): Promise<string>;
|
|
459
485
|
private formatContextLines;
|
|
460
486
|
private writeContextSyncFile;
|