openclaw-lark-multi-agent 1.4.7 → 1.4.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/README.md +35 -16
- package/README.zh-CN.md +17 -1
- package/dist/cli.js +4 -4
- package/dist/feishu-bot.d.ts +4 -0
- package/dist/feishu-bot.js +151 -67
- package/dist/live-status.d.ts +11 -0
- package/dist/live-status.js +58 -4
- package/dist/openclaw-client.d.ts +26 -9
- package/dist/openclaw-client.js +280 -62
- package/dist/session-status.d.ts +18 -0
- package/dist/session-status.js +39 -0
- package/package.json +4 -4
- package/plugins/lma-steer/README.md +28 -21
- package/plugins/lma-steer/openclaw.plugin.json +57 -2
- package/plugins/lma-steer/package.json +4 -2
package/dist/live-status.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { FOREGROUND_WAIT_MS } from "./session-status.js";
|
|
1
2
|
const DEFAULT_DELAY_MS = Number(process.env.OPENCLAW_LARK_MULTI_AGENT_LIVE_STATUS_DELAY_MS || 800);
|
|
2
3
|
const DEFAULT_MAX_CHARS = Number(process.env.OPENCLAW_LARK_MULTI_AGENT_LIVE_STATUS_MAX_CHARS || 120);
|
|
3
4
|
// How many recent activity lines to keep in the card content area.
|
|
@@ -22,8 +23,11 @@ export class LiveStatusController {
|
|
|
22
23
|
startedAt = Date.now();
|
|
23
24
|
createTimer;
|
|
24
25
|
tickTimer;
|
|
26
|
+
refreshBudgetTimer;
|
|
25
27
|
createPromise;
|
|
26
28
|
finalized = false;
|
|
29
|
+
waitingForResult = false;
|
|
30
|
+
updatesFrozen = false;
|
|
27
31
|
disabled = false;
|
|
28
32
|
/** Consecutive safeEdit failures. A single transient Feishu error (e.g. card
|
|
29
33
|
* patch code=2200 "Internal Error") must NOT permanently disable the card:
|
|
@@ -42,9 +46,15 @@ export class LiveStatusController {
|
|
|
42
46
|
}
|
|
43
47
|
get id() { return this.messageId; }
|
|
44
48
|
start(initialDetail) {
|
|
45
|
-
if (this.disabled || this.finalized || this.createTimer || this.messageId)
|
|
49
|
+
if (this.disabled || this.finalized || this.updatesFrozen || this.createTimer || this.messageId)
|
|
46
50
|
return;
|
|
47
51
|
this.startedAt = Date.now();
|
|
52
|
+
this.refreshBudgetTimer = setTimeout(() => {
|
|
53
|
+
void this.showWaitingForResult(this.opts.locale === "en"
|
|
54
|
+
? "Foreground refresh budget reached; background results are still monitored."
|
|
55
|
+
: "前台刷新时间已到,过程卡停止实时刷新;后台继续接收结果。").catch(err => this.callbacks.warn?.("live status freeze failed", err));
|
|
56
|
+
}, this.opts.refreshBudgetMs ?? FOREGROUND_WAIT_MS);
|
|
57
|
+
this.refreshBudgetTimer.unref?.();
|
|
48
58
|
if (initialDetail && initialDetail.trim()) {
|
|
49
59
|
this.pushLine("lifecycle", initialDetail.trim());
|
|
50
60
|
}
|
|
@@ -56,6 +66,12 @@ export class LiveStatusController {
|
|
|
56
66
|
async progress(event) {
|
|
57
67
|
if (this.disabled || this.finalized)
|
|
58
68
|
return;
|
|
69
|
+
if (this.updatesFrozen) {
|
|
70
|
+
// Keep only the cheap local counter for the eventual one-time summary.
|
|
71
|
+
if (typeof event !== "string" && event.kind === "tool" && event.phase === "start")
|
|
72
|
+
this.toolCallCount++;
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
59
75
|
// Card patch has no 20-edit cap (verified against Feishu im.message.patch),
|
|
60
76
|
// so we can show a small rolling window of recent activity: tool start, tool
|
|
61
77
|
// end, and intermediate assistant text each count as one line. We ignore
|
|
@@ -96,9 +112,41 @@ export class LiveStatusController {
|
|
|
96
112
|
else {
|
|
97
113
|
return; // lifecycle ticks: ignore (footer timer already advances)
|
|
98
114
|
}
|
|
115
|
+
this.waitingForResult = false;
|
|
99
116
|
await this.ensureCreatedNow();
|
|
100
117
|
await this.safeEdit(this.buildView());
|
|
101
118
|
}
|
|
119
|
+
/** Freeze recurring/progress edits, not the original result observer.
|
|
120
|
+
* A single final/error summary remains allowed when the real outcome arrives. */
|
|
121
|
+
async showWaitingForResult(detail) {
|
|
122
|
+
if (this.disabled || this.finalized || this.updatesFrozen)
|
|
123
|
+
return;
|
|
124
|
+
this.updatesFrozen = true;
|
|
125
|
+
this.waitingForResult = true;
|
|
126
|
+
this.pushLine("lifecycle", detail);
|
|
127
|
+
this.stopTimers();
|
|
128
|
+
if (this.createPromise)
|
|
129
|
+
await this.createPromise.catch(() => { });
|
|
130
|
+
this.stopTimers();
|
|
131
|
+
if (this.messageId && !this.disabled)
|
|
132
|
+
await this.safeEditFinal(this.buildView());
|
|
133
|
+
}
|
|
134
|
+
/** Release only local observation of a now-idle session, without claiming
|
|
135
|
+
* the previous task succeeded or was aborted. Normal progress is unchanged. */
|
|
136
|
+
async pauseForUnconfirmedResult(detail, title) {
|
|
137
|
+
if (this.finalized)
|
|
138
|
+
return;
|
|
139
|
+
this.pushLine("lifecycle", detail);
|
|
140
|
+
this.finalized = true;
|
|
141
|
+
this.stopTimers();
|
|
142
|
+
if (this.createPromise)
|
|
143
|
+
await this.createPromise.catch(() => { });
|
|
144
|
+
if (!this.messageId || this.disabled)
|
|
145
|
+
return;
|
|
146
|
+
const view = this.buildView();
|
|
147
|
+
view.title = title || (this.opts.locale === "en" ? `${this.opts.botName} result unconfirmed` : `${this.opts.botName} 结果未确认`);
|
|
148
|
+
await this.safeEditFinal(view);
|
|
149
|
+
}
|
|
102
150
|
async complete() {
|
|
103
151
|
this.state = "done";
|
|
104
152
|
this.finalized = true;
|
|
@@ -205,6 +253,10 @@ export class LiveStatusController {
|
|
|
205
253
|
this.lines = this.lines.slice(this.lines.length - limit);
|
|
206
254
|
}
|
|
207
255
|
stopTimers() {
|
|
256
|
+
if (this.refreshBudgetTimer) {
|
|
257
|
+
clearTimeout(this.refreshBudgetTimer);
|
|
258
|
+
this.refreshBudgetTimer = undefined;
|
|
259
|
+
}
|
|
208
260
|
if (this.createTimer) {
|
|
209
261
|
clearTimeout(this.createTimer);
|
|
210
262
|
this.createTimer = undefined;
|
|
@@ -244,11 +296,11 @@ export class LiveStatusController {
|
|
|
244
296
|
}
|
|
245
297
|
/** Refresh the elapsed-time footer periodically even with no new activity. */
|
|
246
298
|
startTicker() {
|
|
247
|
-
if (this.tickTimer || this.disabled || this.finalized)
|
|
299
|
+
if (this.tickTimer || this.disabled || this.finalized || this.updatesFrozen)
|
|
248
300
|
return;
|
|
249
301
|
const tickMs = this.opts.tickMs ?? DEFAULT_TICK_MS;
|
|
250
302
|
this.tickTimer = setInterval(() => {
|
|
251
|
-
if (this.finalized || this.disabled || !this.messageId)
|
|
303
|
+
if (this.finalized || this.disabled || this.updatesFrozen || !this.messageId)
|
|
252
304
|
return;
|
|
253
305
|
void this.safeEdit(this.buildView());
|
|
254
306
|
}, tickMs);
|
|
@@ -257,7 +309,7 @@ export class LiveStatusController {
|
|
|
257
309
|
async safeEdit(view, force = false) {
|
|
258
310
|
if (!this.messageId || this.disabled)
|
|
259
311
|
return false;
|
|
260
|
-
if (!force && this.finalized)
|
|
312
|
+
if (!force && (this.finalized || this.updatesFrozen))
|
|
261
313
|
return false;
|
|
262
314
|
const sig = this.signature(view);
|
|
263
315
|
if (!force && sig === this.lastSentSignature)
|
|
@@ -338,6 +390,8 @@ export class LiveStatusController {
|
|
|
338
390
|
title = en ? `\u2705 ${this.opts.botName} done` : `\u2705 ${this.opts.botName} \u5df2\u5b8c\u6210`;
|
|
339
391
|
else if (this.state === "failed")
|
|
340
392
|
title = en ? `\u26A0\uFE0F ${this.opts.botName} stopped` : `\u26A0\uFE0F ${this.opts.botName} \u6267\u884c\u4e2d\u65ad`;
|
|
393
|
+
else if (this.waitingForResult)
|
|
394
|
+
title = en ? `${this.opts.botName} waiting (card frozen)` : `${this.opts.botName} 等待后续结果(已停止刷新)`;
|
|
341
395
|
else
|
|
342
396
|
title = en ? `${this.opts.botName} is working` : `${this.opts.botName} \u6b63\u5728\u6267\u884c`;
|
|
343
397
|
// On a clean finish (done / NO_REPLY) show only the compact summary. On a
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { SessionWaitPaused, type SessionRuntimeStatus } from "./session-status.js";
|
|
1
2
|
import { OpenClawConfig } from "./config.js";
|
|
2
3
|
import { ChatMessage } from "./message-store.js";
|
|
3
4
|
export type ProgressEvent = {
|
|
@@ -15,6 +16,9 @@ export type ProgressEvent = {
|
|
|
15
16
|
kind: "lifecycle";
|
|
16
17
|
text: string;
|
|
17
18
|
};
|
|
19
|
+
export declare class InactiveRunObservation extends Error {
|
|
20
|
+
constructor();
|
|
21
|
+
}
|
|
18
22
|
export type ChatAttachment = {
|
|
19
23
|
type?: string;
|
|
20
24
|
mimeType?: string;
|
|
@@ -37,6 +41,8 @@ export declare class OpenClawClient {
|
|
|
37
41
|
private reconnectDelay;
|
|
38
42
|
private maxReconnectDelay;
|
|
39
43
|
private shouldReconnect;
|
|
44
|
+
/** Read-only reconciliation for a collector whose plugin reports no active run. */
|
|
45
|
+
private runStateChecks;
|
|
40
46
|
/** Callbacks for tool events (verbose mode) */
|
|
41
47
|
private toolEventCallbacks;
|
|
42
48
|
private lastToolNamesByItemId;
|
|
@@ -102,8 +108,9 @@ export declare class OpenClawClient {
|
|
|
102
108
|
* Collect agent reply by polling accumulated events.
|
|
103
109
|
* Matches by initial runId OR sessionKey to handle multi-turn tool calling
|
|
104
110
|
* where OpenClaw creates new runIds for each tool-call round.
|
|
105
|
-
*
|
|
106
|
-
*
|
|
111
|
+
* Existing event/anchor matching is retained. After 30 minutes of silence,
|
|
112
|
+
* reconcile the original run instead of aborting or treating a draft as final.
|
|
113
|
+
* Explicit terminal/error/disconnect paths keep their existing behavior.
|
|
107
114
|
*/
|
|
108
115
|
private collectReply;
|
|
109
116
|
/**
|
|
@@ -137,13 +144,17 @@ export declare class OpenClawClient {
|
|
|
137
144
|
label?: string;
|
|
138
145
|
}): Promise<any>;
|
|
139
146
|
/**
|
|
140
|
-
* Submit a mid-run message through
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
*
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
-
*
|
|
147
|
+
* Submit a mid-run message through the lma-steer plugin.
|
|
148
|
+
*
|
|
149
|
+
* OpenClaw's public `chat.send { queueMode: "steer" }` returns `started` even
|
|
150
|
+
* when it falls through to a later ordinary run; it therefore cannot tell the
|
|
151
|
+
* bridge whether THIS message entered the active run. The plugin resolves the
|
|
152
|
+
* active embedded run and reports `steered` only when it actually queued there.
|
|
153
|
+
* A matching session.message remains the final consumption confirmation.
|
|
154
|
+
*
|
|
155
|
+
* If the plugin is missing, stale, or rejects the injection, return unavailable
|
|
156
|
+
* and let LMA's durable trigger run normally after the current turn. Never use
|
|
157
|
+
* the ambiguous public admission as a realtime-insertion success signal.
|
|
147
158
|
*/
|
|
148
159
|
steer(sessionKey: string, text: string, displayText?: string): Promise<{
|
|
149
160
|
status: "steered" | "unavailable";
|
|
@@ -175,6 +186,10 @@ export declare class OpenClawClient {
|
|
|
175
186
|
/**
|
|
176
187
|
* Get session info (model, tokens, etc.) for status display.
|
|
177
188
|
*/
|
|
189
|
+
/** Stop claiming an exact run's final output after observation has paused. */
|
|
190
|
+
releasePausedWait(runId: string): void;
|
|
191
|
+
getSessionRuntimeStatus(sessionKey: string): Promise<SessionRuntimeStatus>;
|
|
192
|
+
private classifyWaitTimeout;
|
|
178
193
|
getSessionInfo(sessionKey: string): Promise<any>;
|
|
179
194
|
/**
|
|
180
195
|
* Ensure session is using the expected model. If not, patch it back.
|
|
@@ -218,6 +233,7 @@ export declare class OpenClawClient {
|
|
|
218
233
|
/** Called after chat.send RPC succeeds and OpenClaw has accepted the user message. */
|
|
219
234
|
onSubmitted?: (runId: string) => void | Promise<void>;
|
|
220
235
|
onProgress?: (event: ProgressEvent) => void | Promise<void>;
|
|
236
|
+
onWaitPaused?: (notice: SessionWaitPaused) => void | Promise<void>;
|
|
221
237
|
}): Promise<string>;
|
|
222
238
|
private shouldInjectBridgeAttachmentHint;
|
|
223
239
|
private bridgeAttachmentHint;
|
|
@@ -251,6 +267,7 @@ export declare class OpenClawClient {
|
|
|
251
267
|
/** Called after chat.send RPC succeeds and OpenClaw has accepted the combined message. */
|
|
252
268
|
onSubmitted?: (runId: string) => void | Promise<void>;
|
|
253
269
|
onProgress?: (event: ProgressEvent) => void | Promise<void>;
|
|
270
|
+
onWaitPaused?: (notice: SessionWaitPaused) => void | Promise<void>;
|
|
254
271
|
}): Promise<string>;
|
|
255
272
|
private formatContextLines;
|
|
256
273
|
private writeContextSyncFile;
|