openclaw-lark-multi-agent 1.8.10 → 1.8.12
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 +18 -17
- package/dist/openclaw-client.d.ts +10 -0
- package/dist/openclaw-client.js +173 -14
- package/docs/releases/1.8.11.md +52 -0
- package/package.json +3 -2
package/dist/feishu-bot.js
CHANGED
|
@@ -1319,11 +1319,20 @@ export class FeishuBot {
|
|
|
1319
1319
|
return;
|
|
1320
1320
|
// Session creation/model setup is not agent admission. Initialize a truly
|
|
1321
1321
|
// new chat before asking for its live activity; never infer idle from a
|
|
1322
|
-
// missing describe response.
|
|
1322
|
+
// missing describe response. A failed setup is not dispatch-idle evidence;
|
|
1323
|
+
// retain this never-attempted input for paced reconciliation as well.
|
|
1323
1324
|
try {
|
|
1324
1325
|
await this.ensureSession(chatId);
|
|
1325
1326
|
}
|
|
1326
1327
|
catch {
|
|
1328
|
+
if (unsubmitted() && !this.queueRuns.has(chatId) && !this.store.getDispatchCustody(this.config.name, chatId)) {
|
|
1329
|
+
const row = this.store.getInsertion(this.config.name, chatId, rowId);
|
|
1330
|
+
if (!row || row.state === 'pending' && row.activityUnbound && !row.attempted && !row.expectedRunId)
|
|
1331
|
+
this.store.saveInsertion(this.config.name, chatId, rowId, row?.insertionId || JSON.stringify([this.config.appId, chatId, messageId]), 'pending', {
|
|
1332
|
+
messageId, text, stopEpoch: epoch, sessionKey: this.getSessionKey(chatId), activityUnbound: true, attempted: false, expectedRunId: '',
|
|
1333
|
+
reason: 'session setup unavailable; retained without dispatch-idle proof',
|
|
1334
|
+
}, Date.now() + 5000);
|
|
1335
|
+
}
|
|
1327
1336
|
return;
|
|
1328
1337
|
}
|
|
1329
1338
|
if (!unsubmitted() || this.queueRuns.has(chatId) || this.store.getDispatchCustody(this.config.name, chatId))
|
|
@@ -1344,8 +1353,8 @@ export class FeishuBot {
|
|
|
1344
1353
|
if (current ? fresh?.insertionId !== current.insertionId || fresh.state !== 'pending' || fresh.attempted === true || !fresh.activityUnbound || !!fresh.expectedRunId : !!fresh)
|
|
1345
1354
|
return;
|
|
1346
1355
|
const compatible = !activity.sessionId || !status.sessionId || activity.sessionId === status.sessionId;
|
|
1347
|
-
const idle = compatible && !status.archived && !activity.archived && !activity.busyConfirmed &&
|
|
1348
|
-
|
|
1356
|
+
const idle = compatible && !status.running && !status.archived && !activity.archived && !activity.busyConfirmed &&
|
|
1357
|
+
activity.idleConfirmed === true && !!activity.sessionId;
|
|
1349
1358
|
if (idle) {
|
|
1350
1359
|
if (current)
|
|
1351
1360
|
this.store.saveInsertion(this.config.name, chatId, rowId, current.insertionId, 'ordinary', { ...current, activityUnbound: false });
|
|
@@ -1374,11 +1383,9 @@ export class FeishuBot {
|
|
|
1374
1383
|
return this.stopGeneration(chatId) === epoch && now?.id === saved.id && now?.batchId === saved.batchId && now?.runId === saved.runId &&
|
|
1375
1384
|
this.queueOwners.get(chatId) === local && (!local || local.batch === batch && local.runId === runId && (local.admitted === true || local.yielded === true) && local.collecting === true);
|
|
1376
1385
|
};
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
return false;
|
|
1381
|
-
}
|
|
1386
|
+
const activity = await this.openclawClient.getSessionDispatchStatus?.(this.getSessionKey(chatId)).catch(() => null);
|
|
1387
|
+
if (!unchanged() || !activity?.idleConfirmed || activity.busyConfirmed || activity.archived || activity.sessionId !== saved.sessionId)
|
|
1388
|
+
return false;
|
|
1382
1389
|
const status = await this.openclawClient.getSessionRuntimeStatus(this.getSessionKey(chatId)).catch(() => null);
|
|
1383
1390
|
if (!unchanged() || !status?.idleConfirmed || status.archived || status.sessionId !== saved.sessionId)
|
|
1384
1391
|
return false;
|
|
@@ -1556,18 +1563,12 @@ export class FeishuBot {
|
|
|
1556
1563
|
const status = await this.openclawClient.getSessionRuntimeStatus(item.sessionKey).catch(() => null);
|
|
1557
1564
|
if (!stillCurrent())
|
|
1558
1565
|
continue;
|
|
1559
|
-
const
|
|
1560
|
-
if (!stillCurrent())
|
|
1561
|
-
continue;
|
|
1562
|
-
const activity = !owner && !rootSettled && status?.idleConfirmed ? await this.openclawClient.getSessionDispatchStatus?.(item.sessionKey).catch(() => null) : null;
|
|
1566
|
+
const activity = status?.idleConfirmed ? await this.openclawClient.getSessionDispatchStatus?.(item.sessionKey).catch(() => null) : null;
|
|
1563
1567
|
if (!stillCurrent())
|
|
1564
1568
|
continue;
|
|
1565
|
-
const currentIdle = activity?.idleConfirmed === true && activity.sessionId === item.sessionId && !activity.archived && status?.sessionId === item.sessionId;
|
|
1569
|
+
const currentIdle = activity?.idleConfirmed === true && !activity.busyConfirmed && activity.sessionId === item.sessionId && !activity.archived && status?.sessionId === item.sessionId;
|
|
1566
1570
|
const unchanged = this.queueOwners.get(chatId) === owner && owner?.batch === batchAtCheck && owner?.runId === runAtCheck;
|
|
1567
|
-
|
|
1568
|
-
const dispatchReleased = target?.dispatchReleased === true && target.dispatchOwnerId === item.ownerId && target.dispatchBatchId === item.batchId &&
|
|
1569
|
-
target.sessionId === item.sessionId && status?.sessionId === item.sessionId && !status?.archived;
|
|
1570
|
-
if ((rootSettled === true || dispatchReleased || currentIdle) && status?.idleConfirmed === true && unchanged) {
|
|
1571
|
+
if (currentIdle && status?.idleConfirmed === true && unchanged) {
|
|
1571
1572
|
state = 'ordinary';
|
|
1572
1573
|
// Never expire a successor merely to schedule the old input.
|
|
1573
1574
|
if (runAtCheck === item.expectedRunId)
|
|
@@ -71,6 +71,7 @@ export declare class OpenClawClient {
|
|
|
71
71
|
/** Deduplicates 2026.8 canonical `agent.stream=tool` events against legacy
|
|
72
72
|
* `stream=item` mirrors when a runtime emits both representations. */
|
|
73
73
|
private recentToolEventKeys;
|
|
74
|
+
private privateInputToolCalls;
|
|
74
75
|
private sessionSettlementCallbacks;
|
|
75
76
|
private sessionMessageCallbacks;
|
|
76
77
|
private progressCallbacks;
|
|
@@ -138,9 +139,18 @@ export declare class OpenClawClient {
|
|
|
138
139
|
connect(): Promise<void>;
|
|
139
140
|
private _doConnect;
|
|
140
141
|
private extractVisibleUserText;
|
|
142
|
+
/** Role is presentation, not authorship. Core's public forwarded-message
|
|
143
|
+
* projection preserves these input markers even when it changes user to
|
|
144
|
+
* assistant. Stream events without a message/role remain supported; never
|
|
145
|
+
* inspect run-level inputProvenance (a real reply may answer such an input). */
|
|
146
|
+
private isInputRecord;
|
|
141
147
|
private extractVisibleAssistantText;
|
|
142
148
|
private isVerboseTranscriptEnabled;
|
|
143
149
|
private clearVerboseAssistantState;
|
|
150
|
+
/** Internal message tools carry private input, not assistant narration.
|
|
151
|
+
* Redact before buffering as well as progress callbacks (including failure
|
|
152
|
+
* summaries). Preserve tool identity/phase for activity and acceptance logic. */
|
|
153
|
+
private redactPrivateInputTool;
|
|
144
154
|
/** Render protocol-4 structured tool args/results without `[object Object]`.
|
|
145
155
|
* Prefer useful, non-secret fields and keep the fallback bounded. */
|
|
146
156
|
private formatToolValue;
|
package/dist/openclaw-client.js
CHANGED
|
@@ -82,6 +82,7 @@ export class OpenClawClient {
|
|
|
82
82
|
/** Deduplicates 2026.8 canonical `agent.stream=tool` events against legacy
|
|
83
83
|
* `stream=item` mirrors when a runtime emits both representations. */
|
|
84
84
|
recentToolEventKeys = new Map();
|
|
85
|
+
privateInputToolCalls = new Set();
|
|
85
86
|
sessionSettlementCallbacks = new Map();
|
|
86
87
|
sessionMessageCallbacks = new Map();
|
|
87
88
|
progressCallbacks = new Map();
|
|
@@ -344,6 +345,16 @@ export class OpenClawClient {
|
|
|
344
345
|
p.reject(new Error(`RPC error: ${JSON.stringify(frame.error)}`));
|
|
345
346
|
}
|
|
346
347
|
}
|
|
348
|
+
if (frame.event === 'agent' && ['tool', 'item'].includes(frame.payload?.stream))
|
|
349
|
+
this.redactPrivateInputTool(frame.payload);
|
|
350
|
+
// Public transcript projections can render an INPUT as role=assistant
|
|
351
|
+
// (forwarded sessions_send / internal receipts). Reject its structured
|
|
352
|
+
// identity before any collector, proactive callback or progress buffer.
|
|
353
|
+
// Do not classify by text, run ownership, or a wrapping prompt prefix:
|
|
354
|
+
// genuine replies/quotes and external Cron/child outputs remain valid.
|
|
355
|
+
if ((frame.event === 'chat' ||
|
|
356
|
+
frame.event === 'agent' && ['assistant', 'chatDelta'].includes(frame.payload?.stream)) && this.isInputRecord(frame.payload))
|
|
357
|
+
return;
|
|
347
358
|
// Agent events — store per session key
|
|
348
359
|
if (frame.event === "agent" || frame.event === "chat") {
|
|
349
360
|
const sk = frame.payload?.sessionKey || "__default__";
|
|
@@ -412,7 +423,8 @@ export class OpenClawClient {
|
|
|
412
423
|
if (bucket.length > 2048)
|
|
413
424
|
bucket.splice(0, bucket.length - 2048);
|
|
414
425
|
}
|
|
415
|
-
if (frame.type === "event" && ["agent", "chat", "session.message"].includes(frame.event)
|
|
426
|
+
if (frame.type === "event" && ["agent", "chat", "session.message"].includes(frame.event) &&
|
|
427
|
+
!(frame.event === 'session.message' && this.isInputRecord(frame.payload))) {
|
|
416
428
|
for (const observer of this.goEvidenceObservers) {
|
|
417
429
|
try {
|
|
418
430
|
observer({ channel: frame.event, payload: frame.payload });
|
|
@@ -448,7 +460,8 @@ export class OpenClawClient {
|
|
|
448
460
|
// in correct time order and flip the reaction to Get.
|
|
449
461
|
this.handleSteerConsumption(rawKey, visibleUserText);
|
|
450
462
|
}
|
|
451
|
-
const
|
|
463
|
+
const inputRecord = this.isInputRecord(frame.payload);
|
|
464
|
+
const visibleAssistantText = inputRecord ? "" : this.extractVisibleAssistantText(msg);
|
|
452
465
|
if (visibleAssistantText) {
|
|
453
466
|
if (!this.agentEvents.has(rawKey))
|
|
454
467
|
this.agentEvents.set(rawKey, []);
|
|
@@ -459,7 +472,8 @@ export class OpenClawClient {
|
|
|
459
472
|
data: { deltaText: visibleAssistantText, delta: visibleAssistantText, replace: true },
|
|
460
473
|
});
|
|
461
474
|
}
|
|
462
|
-
|
|
475
|
+
if (!inputRecord)
|
|
476
|
+
this.handleProactiveSessionMessage(rawKey, msg, frame.payload.runId, frame.payload.sessionId);
|
|
463
477
|
for (const wake of this.eventCollectors)
|
|
464
478
|
wake();
|
|
465
479
|
// Tool calls in assistant messages — skip, using agent item events instead
|
|
@@ -598,8 +612,26 @@ export class OpenClawClient {
|
|
|
598
612
|
.join("\n")
|
|
599
613
|
.trim();
|
|
600
614
|
}
|
|
615
|
+
/** Role is presentation, not authorship. Core's public forwarded-message
|
|
616
|
+
* projection preserves these input markers even when it changes user to
|
|
617
|
+
* assistant. Stream events without a message/role remain supported; never
|
|
618
|
+
* inspect run-level inputProvenance (a real reply may answer such an input). */
|
|
619
|
+
isInputRecord(record) {
|
|
620
|
+
if (!record || typeof record !== 'object')
|
|
621
|
+
return false;
|
|
622
|
+
if (typeof record.role === 'string' && record.role !== 'assistant')
|
|
623
|
+
return true;
|
|
624
|
+
if (['external_user', 'inter_session', 'internal_system'].includes(record.provenance?.kind))
|
|
625
|
+
return true;
|
|
626
|
+
if (record.senderSession && typeof record.senderSession === 'object')
|
|
627
|
+
return true;
|
|
628
|
+
const inputKey = record.idempotencyKey ?? record.__openclaw?.idempotencyKey;
|
|
629
|
+
if (typeof inputKey === 'string' && inputKey.endsWith(':user'))
|
|
630
|
+
return true;
|
|
631
|
+
return this.isInputRecord(record.message) || this.isInputRecord(record.data);
|
|
632
|
+
}
|
|
601
633
|
extractVisibleAssistantText(msg, options = {}) {
|
|
602
|
-
if (msg?.role !== "assistant")
|
|
634
|
+
if (msg?.role !== "assistant" || this.isInputRecord(msg))
|
|
603
635
|
return "";
|
|
604
636
|
const content = msg.content;
|
|
605
637
|
// Extract only visible text parts and ignore thinking/tool blocks.
|
|
@@ -638,13 +670,48 @@ export class OpenClawClient {
|
|
|
638
670
|
this.verboseAssistantLastTouched.delete(key);
|
|
639
671
|
}
|
|
640
672
|
}
|
|
673
|
+
/** Internal message tools carry private input, not assistant narration.
|
|
674
|
+
* Redact before buffering as well as progress callbacks (including failure
|
|
675
|
+
* summaries). Preserve tool identity/phase for activity and acceptance logic. */
|
|
676
|
+
redactPrivateInputTool(payload) {
|
|
677
|
+
const data = payload?.data;
|
|
678
|
+
if (!data || payload.stream === 'item' && data.kind !== 'tool')
|
|
679
|
+
return;
|
|
680
|
+
const name = String(data.name || data.toolName || '').split('.').pop();
|
|
681
|
+
const eventId = String(data.toolCallId || data.itemId || data.id || '').replace(/^(?:tool|command):/, '');
|
|
682
|
+
const key = JSON.stringify([this.canonicalSessionKey(payload.sessionKey || ''), payload.runId || '', eventId]);
|
|
683
|
+
let args = data.input ?? data.args ?? data.meta;
|
|
684
|
+
if (typeof args === 'string') {
|
|
685
|
+
try {
|
|
686
|
+
args = JSON.parse(args);
|
|
687
|
+
}
|
|
688
|
+
catch {
|
|
689
|
+
args = undefined;
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
const wrapped = name === 'tool_call' && /(?:^|[.:])sessions_(?:send|yield)$/.test(String(args?.id || ''));
|
|
693
|
+
const privateBody = name === 'sessions_send' || name === 'sessions_yield' || wrapped;
|
|
694
|
+
if (privateBody && eventId) {
|
|
695
|
+
this.privateInputToolCalls.add(key);
|
|
696
|
+
if (this.privateInputToolCalls.size > 2048)
|
|
697
|
+
this.privateInputToolCalls.delete(this.privateInputToolCalls.values().next().value);
|
|
698
|
+
}
|
|
699
|
+
if (!privateBody && !this.privateInputToolCalls.has(key))
|
|
700
|
+
return;
|
|
701
|
+
payload.data = Object.fromEntries(['kind', 'phase', 'name', 'toolName', 'toolCallId', 'itemId', 'id', 'isError'].filter(k => data[k] !== undefined).map(k => [k, data[k]]));
|
|
702
|
+
}
|
|
641
703
|
/** Render protocol-4 structured tool args/results without `[object Object]`.
|
|
642
704
|
* Prefer useful, non-secret fields and keep the fallback bounded. */
|
|
643
705
|
formatToolValue(value, toolName, phase) {
|
|
644
706
|
// sessions_yield.message is private resumed context. Do not mirror its
|
|
645
707
|
// arguments/results (including JSON strings) into process cards or verbose
|
|
646
708
|
// tool replies. This is redaction only, never evidence of a handoff.
|
|
647
|
-
if (toolName === "sessions_yield")
|
|
709
|
+
if (toolName === "sessions_yield" || toolName === "sessions_send")
|
|
710
|
+
return "";
|
|
711
|
+
// Generic public-tool wrapper: redact only the internal send operation,
|
|
712
|
+
// not all tool bodies or outputs. Its returned body can echo the input.
|
|
713
|
+
if (toolName === 'tool_call' && value && typeof value === 'object' &&
|
|
714
|
+
/(?:^|[.:])sessions_send$/.test(String(value.id || '')))
|
|
648
715
|
return "";
|
|
649
716
|
if (value === null || value === undefined || value === "")
|
|
650
717
|
return "";
|
|
@@ -779,6 +846,8 @@ export class OpenClawClient {
|
|
|
779
846
|
return true;
|
|
780
847
|
}
|
|
781
848
|
handleVerboseAssistantStream(payload) {
|
|
849
|
+
if (this.isInputRecord(payload))
|
|
850
|
+
return;
|
|
782
851
|
const rawKey = payload?.sessionKey || "";
|
|
783
852
|
if (!rawKey)
|
|
784
853
|
return;
|
|
@@ -806,6 +875,8 @@ export class OpenClawClient {
|
|
|
806
875
|
}
|
|
807
876
|
}
|
|
808
877
|
handleProactiveSessionMessage(rawKey, msg, runId, sessionId) {
|
|
878
|
+
if (this.isInputRecord(msg))
|
|
879
|
+
return false;
|
|
809
880
|
const shortKey = rawKey.replace(/^agent:[^:]+:/, "");
|
|
810
881
|
// Proactive assistant text messages. Cron/session-targeted runs often emit
|
|
811
882
|
// structured content arrays rather than a plain string. Extract only visible
|
|
@@ -2123,16 +2194,101 @@ export class OpenClawClient {
|
|
|
2123
2194
|
* terminal caches. Unknown/missing fields never authorize scheduling. */
|
|
2124
2195
|
async getSessionDispatchStatus(sessionKey) {
|
|
2125
2196
|
let timer;
|
|
2197
|
+
const key = this.canonicalSessionKey(sessionKey), deadline = Date.now() + 1500;
|
|
2198
|
+
// One budget covers the entire public pending scan, not 1500ms per page.
|
|
2199
|
+
// Retained interrupted/cancelled inputs are recovery records, NOT runnable
|
|
2200
|
+
// queue entries. Never delete, mutate or replay them to prove schedulability.
|
|
2201
|
+
const scan = async () => {
|
|
2202
|
+
let sessionId, total, before;
|
|
2203
|
+
const seen = new Set(), cursors = new Set();
|
|
2204
|
+
let headSignature = '';
|
|
2205
|
+
const unknown = () => ({ idleConfirmed: false, sessionId });
|
|
2206
|
+
const validId = (value) => value === undefined || typeof value === 'string' && !!value.trim();
|
|
2207
|
+
for (let page = 0; page < 32; page++) {
|
|
2208
|
+
const history = await this.rpc('chat.history', { sessionKey: key, limit: 20, ...(before ? { pendingBefore: before } : {}) }, 1500);
|
|
2209
|
+
if (Date.now() >= deadline)
|
|
2210
|
+
return unknown();
|
|
2211
|
+
const info = history?.sessionInfo;
|
|
2212
|
+
const id = info?.sessionId || history?.sessionId;
|
|
2213
|
+
if (!validId(info?.sessionId) || !validId(history?.sessionId) || history?.sessionKey !== key || typeof id !== 'string' || !id.trim() ||
|
|
2214
|
+
info?.sessionId && history?.sessionId && info.sessionId !== history.sessionId || sessionId && sessionId !== id)
|
|
2215
|
+
return unknown();
|
|
2216
|
+
sessionId = id;
|
|
2217
|
+
const base = { sessionId, archived: info?.archived === true };
|
|
2218
|
+
if (base.archived)
|
|
2219
|
+
return { ...base, idleConfirmed: false };
|
|
2220
|
+
if (info?.hasActiveRun === true || history?.inFlightRun)
|
|
2221
|
+
return { ...base, idleConfirmed: false, busyConfirmed: true,
|
|
2222
|
+
activeRunId: typeof history?.inFlightRun?.runId === 'string' ? history.inFlightRun.runId : undefined };
|
|
2223
|
+
const pending = history?.pendingInputs;
|
|
2224
|
+
if (!Number.isSafeInteger(pending?.total) || pending.total < 0 || total !== undefined && total !== pending.total)
|
|
2225
|
+
return unknown();
|
|
2226
|
+
total = pending.total;
|
|
2227
|
+
if (pending.queuedCount !== undefined && (!Number.isSafeInteger(pending.queuedCount) || pending.queuedCount < 0))
|
|
2228
|
+
return unknown();
|
|
2229
|
+
if (pending.queuedCount > 0)
|
|
2230
|
+
return { ...base, idleConfirmed: false, busyConfirmed: true };
|
|
2231
|
+
// Legacy empty pending responses omit items. Only total=0 can safely
|
|
2232
|
+
// support that shape; a hidden/nonempty retained set is never guessed.
|
|
2233
|
+
const items = pending.items === undefined && total === 0 ? [] : pending.items;
|
|
2234
|
+
if (!Array.isArray(items))
|
|
2235
|
+
return unknown();
|
|
2236
|
+
for (const item of items) {
|
|
2237
|
+
if (typeof item?.id !== 'string' || !item.id || !['queued', 'cancelled', 'interrupted'].includes(item.state) || seen.has(item.id))
|
|
2238
|
+
return unknown();
|
|
2239
|
+
seen.add(item.id);
|
|
2240
|
+
if (item.state === 'queued')
|
|
2241
|
+
return { ...base, idleConfirmed: false, busyConfirmed: true };
|
|
2242
|
+
}
|
|
2243
|
+
if (seen.size > total)
|
|
2244
|
+
return unknown();
|
|
2245
|
+
// Every page must positively confirm inactivity. Otherwise a malformed
|
|
2246
|
+
// head/middle page could be laundered by a valid final page.
|
|
2247
|
+
if (info?.hasActiveRun !== false)
|
|
2248
|
+
return unknown();
|
|
2249
|
+
const next = pending.nextBefore;
|
|
2250
|
+
if (next !== undefined && (!Number.isSafeInteger(next) || next <= 0 || cursors.has(next) || before !== undefined && next >= before))
|
|
2251
|
+
return unknown();
|
|
2252
|
+
const signature = JSON.stringify([id, total, info?.hasActiveRun, items.map((item) => [item.id, item.state]), next]);
|
|
2253
|
+
if (page === 0)
|
|
2254
|
+
headSignature = signature;
|
|
2255
|
+
if (next !== undefined) {
|
|
2256
|
+
cursors.add(next);
|
|
2257
|
+
before = next;
|
|
2258
|
+
continue;
|
|
2259
|
+
}
|
|
2260
|
+
if (info?.hasActiveRun !== false || seen.size !== total)
|
|
2261
|
+
return unknown();
|
|
2262
|
+
if (page > 0) {
|
|
2263
|
+
// Pagination is not an atomic snapshot. Re-read the current head and
|
|
2264
|
+
// identity/activity before returning positive evidence to local CAS.
|
|
2265
|
+
const head = await this.rpc('chat.history', { sessionKey: key, limit: 20 }, 1500);
|
|
2266
|
+
if (Date.now() >= deadline)
|
|
2267
|
+
return unknown();
|
|
2268
|
+
const hi = head?.sessionInfo, hp = head?.pendingInputs, hid = hi?.sessionId || head?.sessionId;
|
|
2269
|
+
if (!validId(hi?.sessionId) || !validId(head?.sessionId) || head?.sessionKey !== key || hid !== id || hi?.sessionId && head?.sessionId && hi.sessionId !== head.sessionId)
|
|
2270
|
+
return unknown();
|
|
2271
|
+
if (hi?.archived === true)
|
|
2272
|
+
return { ...base, archived: true, idleConfirmed: false };
|
|
2273
|
+
if (hi?.hasActiveRun === true || head?.inFlightRun || Number.isSafeInteger(hp?.queuedCount) && hp.queuedCount > 0)
|
|
2274
|
+
return { ...base, idleConfirmed: false, busyConfirmed: true, activeRunId: typeof head?.inFlightRun?.runId === 'string' ? head.inFlightRun.runId : undefined };
|
|
2275
|
+
if (hi?.hasActiveRun !== false)
|
|
2276
|
+
return unknown();
|
|
2277
|
+
if (hp?.total !== total || hp?.queuedCount !== undefined && (!Number.isSafeInteger(hp.queuedCount) || hp.queuedCount < 0))
|
|
2278
|
+
return unknown();
|
|
2279
|
+
if (Array.isArray(hp?.items) && hp.items.some((item) => typeof item?.id === 'string' && item.id && item.state === 'queued'))
|
|
2280
|
+
return { ...base, idleConfirmed: false, busyConfirmed: true };
|
|
2281
|
+
if (!Array.isArray(hp?.items) || JSON.stringify([hid, hp.total, hi?.hasActiveRun, hp.items.map((item) => [item?.id, item?.state]), hp.nextBefore]) !== headSignature)
|
|
2282
|
+
return unknown();
|
|
2283
|
+
}
|
|
2284
|
+
return { ...base, idleConfirmed: true, busyConfirmed: false };
|
|
2285
|
+
}
|
|
2286
|
+
return unknown();
|
|
2287
|
+
};
|
|
2126
2288
|
try {
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
const info = history?.sessionInfo;
|
|
2131
|
-
return { idleConfirmed: history?.sessionKey === key && info?.hasActiveRun === false &&
|
|
2132
|
-
history?.pendingInputs?.total === 0 && !history?.inFlightRun && info?.archived !== true,
|
|
2133
|
-
sessionId: info?.sessionId || history?.sessionId, archived: info?.archived === true,
|
|
2134
|
-
activeRunId: history?.sessionKey === key && typeof history?.inFlightRun?.runId === 'string' ? history.inFlightRun.runId : undefined,
|
|
2135
|
-
busyConfirmed: history?.sessionKey === key && (info?.hasActiveRun === true || !!history?.inFlightRun || history?.pendingInputs?.total > 0) };
|
|
2289
|
+
return await Promise.race([scan(), new Promise((_, reject) => {
|
|
2290
|
+
timer = setTimeout(() => reject(new Error('activity lookup timeout')), 1500);
|
|
2291
|
+
})]);
|
|
2136
2292
|
}
|
|
2137
2293
|
catch {
|
|
2138
2294
|
return { idleConfirmed: false };
|
|
@@ -2447,6 +2603,8 @@ export class OpenClawClient {
|
|
|
2447
2603
|
? { state: "stopped", reason: "exact_run_abort_receipt" } : verdict;
|
|
2448
2604
|
}
|
|
2449
2605
|
extractTextFromChatMessage(message) {
|
|
2606
|
+
if (this.isInputRecord(message))
|
|
2607
|
+
return "";
|
|
2450
2608
|
const parts = Array.isArray(message?.content) ? message.content : [];
|
|
2451
2609
|
return parts.filter((part) => part?.type === "text" && typeof part.text === "string").map((part) => part.text).join("\n").trim();
|
|
2452
2610
|
}
|
|
@@ -3147,6 +3305,7 @@ export class OpenClawClient {
|
|
|
3147
3305
|
this.verboseAssistantSent.clear();
|
|
3148
3306
|
this.verboseAssistantLastTouched.clear();
|
|
3149
3307
|
this.lastToolNamesByItemId.clear();
|
|
3308
|
+
this.privateInputToolCalls.clear();
|
|
3150
3309
|
this.recentToolEventKeys.clear();
|
|
3151
3310
|
this.pendingSteerTexts.clear();
|
|
3152
3311
|
this.steerConsumedCallbacks.clear();
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# 1.8.11 — retained pending inputs and output attribution
|
|
2
|
+
|
|
3
|
+
These changes are validated offline. Publication, runtime activation, real-model
|
|
4
|
+
execution and Feishu delivery are separate verification checkpoints.
|
|
5
|
+
|
|
6
|
+
## Correctness changes
|
|
7
|
+
|
|
8
|
+
- Public `pendingInputs.total` counts retained records, not runnable work.
|
|
9
|
+
Complete, identity-matched `interrupted`/`cancelled` records no longer keep a
|
|
10
|
+
session perpetually busy. Queued inputs, real active runs and in-flight work
|
|
11
|
+
still block ordinary admission. Nothing clears or replays Gateway pending
|
|
12
|
+
records, SQLite business rows or stopped work.
|
|
13
|
+
- Dispatch inspection follows `nextBefore` as `pendingBefore`, with a 20-row
|
|
14
|
+
request limit, a finite page cap and one 1500 ms overall budget. Missing or
|
|
15
|
+
hidden records, malformed states/identities, duplicate IDs/cursors and changed
|
|
16
|
+
page identity/total remain unknown. Multi-page idle proof rechecks the head;
|
|
17
|
+
this is conservative observation, not a transactional Gateway snapshot.
|
|
18
|
+
- Every ordinary handoff uses positive matching dispatch-idle evidence, rather
|
|
19
|
+
than allowing describe-only idle to bypass unknown dispatch state. Existing
|
|
20
|
+
owner/batch/session/stop/admission-window fencing remains in force.
|
|
21
|
+
Proven never-attempted local inputs recover through ordinary paced/startup
|
|
22
|
+
admission; accepted, attempted/unknown and stopped inputs are not replayed.
|
|
23
|
+
- Public transcript forwarding can display an input with `role:assistant`.
|
|
24
|
+
Structured input provenance, sender-session markers and public `:user` input
|
|
25
|
+
identities are now excluded before collector, proactive business delivery
|
|
26
|
+
and assistant progress sinks. Missing runId does not turn an input mirror
|
|
27
|
+
into an assistant reply. Internal send/yield tool bodies are also redacted
|
|
28
|
+
before progress/transcript buffering, including identified wrapper results.
|
|
29
|
+
- Filtering is based on structured input identity, not body-prefix stripping,
|
|
30
|
+
quote matching, external run IDs, or the provenance of the *run's input*.
|
|
31
|
+
Genuine assistant partial/final output, literal quotations of the reported
|
|
32
|
+
wrapper, ordinary external/Cron results and real continuation replies retain
|
|
33
|
+
their existing output paths. Input receipts and SDK acceptance callbacks
|
|
34
|
+
retain their separate acknowledgment semantics.
|
|
35
|
+
|
|
36
|
+
## Verification boundaries
|
|
37
|
+
|
|
38
|
+
Offline tests exercise the actual WS receiver, Client/Bot collectors, progress
|
|
39
|
+
cards, durable MessageStore/outbox and public SDK fixtures. Synthetic transports
|
|
40
|
+
are explicitly distinguished from an incident raw-frame capture and from live
|
|
41
|
+
platform acceptance. Public producer modules were inspected only as source;
|
|
42
|
+
no private hashed helper was imported or executed and no OpenClaw host file was
|
|
43
|
+
changed. The coordinated `lma-core` plugin remains **0.2.0**.
|
|
44
|
+
|
|
45
|
+
Deployment, activation and live acceptance remain separate owner-controlled
|
|
46
|
+
steps. This candidate does not repair individual historical business rows or
|
|
47
|
+
retry already-delivered/internal input mirrors.
|
|
48
|
+
|
|
49
|
+
## Follow-up
|
|
50
|
+
|
|
51
|
+
The published 1.8.11 tag does not include the stricter per-page/head inactivity
|
|
52
|
+
and absolute head-deadline checks. Use 1.8.12 for that follow-up correction.
|
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.12",
|
|
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": {
|
|
@@ -74,7 +74,8 @@
|
|
|
74
74
|
"plugins/lma-core/package.json",
|
|
75
75
|
"plugins/lma-core/README.md",
|
|
76
76
|
"docs/releases/1.8.5.md",
|
|
77
|
-
"docs/releases/1.8.10.md"
|
|
77
|
+
"docs/releases/1.8.10.md",
|
|
78
|
+
"docs/releases/1.8.11.md"
|
|
78
79
|
],
|
|
79
80
|
"engines": {
|
|
80
81
|
"node": ">=22"
|