openclaw-lark-multi-agent 1.4.6 → 1.4.8
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 +17 -16
- package/README.zh-CN.md +1 -1
- package/dist/cli.js +4 -4
- package/dist/feishu-bot.js +16 -40
- package/dist/openclaw-client.d.ts +11 -7
- package/dist/openclaw-client.js +19 -24
- package/package.json +1 -1
- package/plugins/lma-steer/README.md +19 -22
package/README.md
CHANGED
|
@@ -101,18 +101,17 @@ lma install-windows-service
|
|
|
101
101
|
|
|
102
102
|
### Real-time steering
|
|
103
103
|
|
|
104
|
-
When the agent is mid-run and a new message arrives, the bridge
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
matching `session.message` proves transcript
|
|
108
|
-
then switches Feishu from Typing to Get.
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
current LMA releases do not call its private Gateway method.
|
|
104
|
+
When the agent is mid-run and a new message arrives, the bridge calls the
|
|
105
|
+
bundled `lma-steer` plugin. The plugin resolves the active embedded run and only
|
|
106
|
+
returns `steered` after queueing the message into that run. LMA then retains its
|
|
107
|
+
durable pending trigger until a matching `session.message` proves transcript
|
|
108
|
+
commitment/consumption, and only then switches Feishu from Typing to Get.
|
|
109
|
+
|
|
110
|
+
OpenClaw's public `chat.send { queueMode: "steer" }` returns `started` even when
|
|
111
|
+
a message falls through to a later ordinary run, so LMA does not use that
|
|
112
|
+
ambiguous response as proof of realtime insertion. If the plugin is unavailable,
|
|
113
|
+
there is no active run, or injection is rejected, the message safely stays in
|
|
114
|
+
the normal queue and is never falsely shown as inserted.
|
|
116
115
|
|
|
117
116
|
Useful CLI commands:
|
|
118
117
|
|
|
@@ -122,7 +121,7 @@ lma doctor
|
|
|
122
121
|
lma start [config]
|
|
123
122
|
lma init [--state-dir DIR] [--force]
|
|
124
123
|
lma install-systemd [--user|--system] [--state-dir DIR]
|
|
125
|
-
lma install-steer-plugin [--no-force] #
|
|
124
|
+
lma install-steer-plugin [--no-force] # required for realtime mid-run insertion
|
|
126
125
|
```
|
|
127
126
|
|
|
128
127
|
## Quick start from source
|
|
@@ -464,9 +463,11 @@ when that list is non-empty. Exact refs and `provider/*` wildcards are accepted.
|
|
|
464
463
|
LMA now surfaces a model-policy rejection instead of logging a false “model
|
|
465
464
|
ensured” success.
|
|
466
465
|
|
|
467
|
-
Mid-run messages use the
|
|
468
|
-
|
|
469
|
-
|
|
466
|
+
Mid-run messages use the bundled `lma-steer` plugin because public
|
|
467
|
+
`chat.send { queueMode: "steer" }` cannot distinguish true in-run insertion from
|
|
468
|
+
a later ordinary run. LMA keeps the durable trigger until `session.message`
|
|
469
|
+
confirms consumption; this is also the signal for the visible Typing-to-Get
|
|
470
|
+
transition. Without the plugin, messages remain safely queued.
|
|
470
471
|
|
|
471
472
|
## Live status
|
|
472
473
|
|
package/README.zh-CN.md
CHANGED
|
@@ -414,7 +414,7 @@ LMA 以 Gateway protocol 4 为基线,并使用 `openclaw@2026.8.2` 构建和
|
|
|
414
414
|
|
|
415
415
|
新版把模型元数据与模型覆盖允许列表分开。如果 `agents.defaults.modelPolicy.allow` 非空,LMA 配置中的每个模型都必须被该列表的精确模型引用或 `provider/*` 通配符允许。遇到拒绝时,LMA 会直接报告 model policy 错误,不再错误记录“模型已确保”。
|
|
416
416
|
|
|
417
|
-
|
|
417
|
+
执行中插入的消息使用内置 `lma-steer` 插件。插件会解析当前 active embedded run,只有消息确实排入该 run 时才返回 `steered`。LMA 仍会保留本地 pending trigger,直到匹配的 `session.message` 确认消息已写入转录并被消费后才清除,同时把飞书标记从 Typing 切换为 Get。公开的 `chat.send { queueMode: "steer" }` 即使退化为稍后的普通 run 也只返回 `started`,因此不再把它当作实时插入成功的证据。插件不可用、没有 active run 或拒绝插入时,消息会安全保留在普通队列中,不会伪报已插入。
|
|
418
418
|
|
|
419
419
|
## Live Status 与最终回复
|
|
420
420
|
|
package/dist/cli.js
CHANGED
|
@@ -206,12 +206,12 @@ function runCapture(cmd, args) {
|
|
|
206
206
|
}
|
|
207
207
|
}
|
|
208
208
|
/**
|
|
209
|
-
* Install the
|
|
210
|
-
*
|
|
211
|
-
* the
|
|
209
|
+
* Install the bundled lma-steer plugin used for observable realtime mid-run
|
|
210
|
+
* insertion. LMA remains functional without it, but busy messages queue until
|
|
211
|
+
* the active run completes.
|
|
212
212
|
*/
|
|
213
213
|
function cmdInstallSteerPlugin(args) {
|
|
214
|
-
console.warn("Note:
|
|
214
|
+
console.warn("Note: lma-steer is required for confirmed realtime mid-run insertion; without it, busy messages remain safely queued.");
|
|
215
215
|
// Default to --force so reinstalling an updated bundled plugin overwrites the
|
|
216
216
|
// previously installed copy. Pass --no-force to keep an existing install.
|
|
217
217
|
const noForce = hasFlag(args, "--no-force");
|
package/dist/feishu-bot.js
CHANGED
|
@@ -968,13 +968,6 @@ export class FeishuBot {
|
|
|
968
968
|
this.pendingAckMessages.set(chatId, acks);
|
|
969
969
|
return true;
|
|
970
970
|
});
|
|
971
|
-
const previousActiveTarget = this.activeDeliveryTargets.get(chatId);
|
|
972
|
-
// Install the new Feishu delivery target before admission. OpenClaw may
|
|
973
|
-
// race from "old run still active" to "dispatch a new run" while
|
|
974
|
-
// handling queueMode=steer, and that new run can emit events before the
|
|
975
|
-
// RPC response reaches LMA. Either outcome now routes to this message.
|
|
976
|
-
const releaseSteerDeliveryTarget = this.setActiveDeliveryTarget(chatId, insertedId, messageId);
|
|
977
|
-
const provisionalTarget = this.activeDeliveryTargets.get(chatId);
|
|
978
971
|
const steer = await this.openclawClient.steer(sessionKeyForSteer, steerWrapped, cleanText).catch(() => ({ status: "unavailable" }));
|
|
979
972
|
if (steer.status === "steered") {
|
|
980
973
|
if (insertedId > 0) {
|
|
@@ -984,39 +977,18 @@ export class FeishuBot {
|
|
|
984
977
|
steer.cancelPending?.();
|
|
985
978
|
});
|
|
986
979
|
}
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
}
|
|
992
|
-
// A steer admission may become an ordinary new run. Keep its run-bound
|
|
993
|
-
// reply target long enough for a real tool-heavy run; stale targets are
|
|
994
|
-
// harmless because later runIds no longer match.
|
|
995
|
-
releaseSteerDeliveryTarget(30 * 60_000);
|
|
996
|
-
// RPC acknowledgment is provisional. Keep the durable pending trigger
|
|
997
|
-
// until session.message confirms consumption; otherwise the normal
|
|
998
|
-
// queue drains it after the current run.
|
|
999
|
-
console.log(`[${this.config.name}] Agent busy for ${chatId.slice(-8)}, steer accepted (awaiting transcript consumption): "${cleanText.substring(0, 50)}..."`);
|
|
980
|
+
// The plugin confirmed queueing into the existing embedded run. Keep
|
|
981
|
+
// that run's original delivery target and retain the durable trigger
|
|
982
|
+
// until session.message proves this specific input was consumed.
|
|
983
|
+
console.log(`[${this.config.name}] Agent busy for ${chatId.slice(-8)}, plugin steer accepted (awaiting transcript consumption): "${cleanText.substring(0, 50)}..."`);
|
|
1000
984
|
return;
|
|
1001
985
|
}
|
|
1002
|
-
//
|
|
1003
|
-
//
|
|
986
|
+
// Plugin missing/no active run/rejected: keep the durable trigger for
|
|
987
|
+
// normal queue drain. Never claim that ambiguous native admission was an
|
|
988
|
+
// in-run insertion.
|
|
1004
989
|
stopWatchingConsumption();
|
|
1005
990
|
if ("cancelPending" in steer)
|
|
1006
991
|
steer.cancelPending?.();
|
|
1007
|
-
const currentTarget = this.activeDeliveryTargets.get(chatId);
|
|
1008
|
-
if (currentTarget && currentTarget.token === provisionalTarget?.token) {
|
|
1009
|
-
if (currentTarget.timer)
|
|
1010
|
-
clearTimeout(currentTarget.timer);
|
|
1011
|
-
this.activeDeliveryTargets.delete(chatId);
|
|
1012
|
-
if (previousActiveTarget) {
|
|
1013
|
-
const releaseRestoredTarget = this.setActiveDeliveryTarget(chatId, previousActiveTarget.triggerId, previousActiveTarget.messageId);
|
|
1014
|
-
const restored = this.activeDeliveryTargets.get(chatId);
|
|
1015
|
-
if (restored)
|
|
1016
|
-
restored.liveStatus = previousActiveTarget.liveStatus;
|
|
1017
|
-
releaseRestoredTarget();
|
|
1018
|
-
}
|
|
1019
|
-
}
|
|
1020
992
|
console.log(`[${this.config.name}] Agent busy for ${chatId.slice(-8)} (steer=${steer.status}), queuing: "${cleanText.substring(0, 50)}..."`);
|
|
1021
993
|
return; // Message is in DB, will be picked up when agent finishes
|
|
1022
994
|
}
|
|
@@ -1825,9 +1797,10 @@ export class FeishuBot {
|
|
|
1825
1797
|
}
|
|
1826
1798
|
completionSummary(meta, emoji = "✅") {
|
|
1827
1799
|
const en = meta.locale === "en";
|
|
1828
|
-
|
|
1800
|
+
const core = en
|
|
1829
1801
|
? `${emoji} ${meta.toolCalls} tool call${meta.toolCalls === 1 ? "" : "s"} · ⏱ ${meta.elapsed}`
|
|
1830
1802
|
: `${emoji} 累计${meta.toolCalls} 次工具调用 · ⏱ 耗时${meta.elapsed}`;
|
|
1803
|
+
return meta.model?.trim() ? `${core} · 🧠 ${this.escapeCardText(meta.model.trim())}` : core;
|
|
1831
1804
|
}
|
|
1832
1805
|
/** Best-effort terminal cleanup for status-only rows and legacy v1.4.3
|
|
1833
1806
|
* outbox entries that once targeted the live-status card. */
|
|
@@ -2654,6 +2627,7 @@ export class FeishuBot {
|
|
|
2654
2627
|
const summary = this.completionSummary({
|
|
2655
2628
|
toolCalls: view.toolCalls,
|
|
2656
2629
|
elapsed: view.elapsed,
|
|
2630
|
+
model: view.model,
|
|
2657
2631
|
locale: en ? "en" : "zh",
|
|
2658
2632
|
}, statusEmoji);
|
|
2659
2633
|
return {
|
|
@@ -2686,10 +2660,12 @@ export class FeishuBot {
|
|
|
2686
2660
|
elements.push({ tag: "markdown", content: en ? "_working…_" : "_正在启动…_" });
|
|
2687
2661
|
}
|
|
2688
2662
|
elements.push({ tag: "hr" });
|
|
2689
|
-
//
|
|
2690
|
-
// reply
|
|
2691
|
-
const
|
|
2692
|
-
|
|
2663
|
+
// Keep the active model visible while the run is in progress or failed.
|
|
2664
|
+
// The final reply also keeps model attribution so it remains self-contained.
|
|
2665
|
+
const footerBits = [en ? `⏱ ${view.elapsed}` : `⏱ 已用 ${view.elapsed}`];
|
|
2666
|
+
if (view.model?.trim())
|
|
2667
|
+
footerBits.push(`🧠 ${this.escapeCardText(view.model.trim())}`);
|
|
2668
|
+
elements.push({ tag: "markdown", content: `<font color='grey'>${footerBits.join(" · ")}</font>` });
|
|
2693
2669
|
const template = view.state === "failed" ? "orange" : "blue";
|
|
2694
2670
|
return {
|
|
2695
2671
|
schema: "2.0",
|
|
@@ -137,13 +137,17 @@ export declare class OpenClawClient {
|
|
|
137
137
|
label?: string;
|
|
138
138
|
}): Promise<any>;
|
|
139
139
|
/**
|
|
140
|
-
* Submit a mid-run message through
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
*
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
-
*
|
|
140
|
+
* Submit a mid-run message through the lma-steer plugin.
|
|
141
|
+
*
|
|
142
|
+
* OpenClaw's public `chat.send { queueMode: "steer" }` returns `started` even
|
|
143
|
+
* when it falls through to a later ordinary run; it therefore cannot tell the
|
|
144
|
+
* bridge whether THIS message entered the active run. The plugin resolves the
|
|
145
|
+
* active embedded run and reports `steered` only when it actually queued there.
|
|
146
|
+
* A matching session.message remains the final consumption confirmation.
|
|
147
|
+
*
|
|
148
|
+
* If the plugin is missing, stale, or rejects the injection, return unavailable
|
|
149
|
+
* and let LMA's durable trigger run normally after the current turn. Never use
|
|
150
|
+
* the ambiguous public admission as a realtime-insertion success signal.
|
|
147
151
|
*/
|
|
148
152
|
steer(sessionKey: string, text: string, displayText?: string): Promise<{
|
|
149
153
|
status: "steered" | "unavailable";
|
package/dist/openclaw-client.js
CHANGED
|
@@ -1228,39 +1228,34 @@ export class OpenClawClient {
|
|
|
1228
1228
|
return this.rpc("chat.inject", { ...params, sessionKey: this.canonicalSessionKey(params.sessionKey) }, 10000);
|
|
1229
1229
|
}
|
|
1230
1230
|
/**
|
|
1231
|
-
* Submit a mid-run message through
|
|
1232
|
-
*
|
|
1233
|
-
*
|
|
1234
|
-
*
|
|
1235
|
-
*
|
|
1236
|
-
*
|
|
1237
|
-
*
|
|
1231
|
+
* Submit a mid-run message through the lma-steer plugin.
|
|
1232
|
+
*
|
|
1233
|
+
* OpenClaw's public `chat.send { queueMode: "steer" }` returns `started` even
|
|
1234
|
+
* when it falls through to a later ordinary run; it therefore cannot tell the
|
|
1235
|
+
* bridge whether THIS message entered the active run. The plugin resolves the
|
|
1236
|
+
* active embedded run and reports `steered` only when it actually queued there.
|
|
1237
|
+
* A matching session.message remains the final consumption confirmation.
|
|
1238
|
+
*
|
|
1239
|
+
* If the plugin is missing, stale, or rejects the injection, return unavailable
|
|
1240
|
+
* and let LMA's durable trigger run normally after the current turn. Never use
|
|
1241
|
+
* the ambiguous public admission as a realtime-insertion success signal.
|
|
1238
1242
|
*/
|
|
1239
1243
|
async steer(sessionKey, text, displayText) {
|
|
1240
1244
|
const key = this.canonicalSessionKey(sessionKey);
|
|
1241
1245
|
try {
|
|
1242
|
-
const res = await this.rpc("
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
idempotencyKey: randomUUID(),
|
|
1248
|
-
}, 10000);
|
|
1249
|
-
const status = res?.status;
|
|
1250
|
-
if (status !== "started" && status !== "in_flight" && status !== "ok") {
|
|
1246
|
+
const res = await this.rpc("lma.steer", { sessionKey: key, text }, 10000);
|
|
1247
|
+
if (res?.status !== "steered") {
|
|
1248
|
+
if (res?.status && res.status !== "no_active_run") {
|
|
1249
|
+
console.warn(`[OpenClaw] lma.steer ${res.status} for ${key.slice(-8)}`);
|
|
1250
|
+
}
|
|
1251
1251
|
return { status: "unavailable" };
|
|
1252
1252
|
}
|
|
1253
|
-
// This records only a provisional accepted input. Actual success is driven
|
|
1254
|
-
// by handleSteerConsumption after the canonical transcript event arrives.
|
|
1255
1253
|
const cancelPending = this.registerPendingSteer(key, text, displayText);
|
|
1256
|
-
return {
|
|
1257
|
-
status: "steered",
|
|
1258
|
-
runId: typeof res?.runId === "string" ? res.runId : undefined,
|
|
1259
|
-
cancelPending,
|
|
1260
|
-
};
|
|
1254
|
+
return { status: "steered", cancelPending };
|
|
1261
1255
|
}
|
|
1262
1256
|
catch (err) {
|
|
1263
|
-
|
|
1257
|
+
const message = err?.message || String(err);
|
|
1258
|
+
console.warn(`[OpenClaw] lma.steer unavailable for ${key.slice(-8)}: ${message}`);
|
|
1264
1259
|
return { status: "unavailable" };
|
|
1265
1260
|
}
|
|
1266
1261
|
}
|
package/package.json
CHANGED
|
@@ -1,38 +1,35 @@
|
|
|
1
|
-
# lma-steer
|
|
1
|
+
# lma-steer
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
`lma-steer` provides observable mid-run insertion for
|
|
4
|
+
`openclaw-lark-multi-agent`.
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
OpenClaw's public `chat.send { queueMode: "steer" }` acknowledges admission with
|
|
7
|
+
`started` even when the message falls through to a later ordinary run. That is
|
|
8
|
+
not enough for LMA to tell users whether a message actually entered the active
|
|
9
|
+
run. This plugin resolves the active embedded run and reports one of:
|
|
8
10
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
11
|
+
- `steered` — queued into the active embedded run;
|
|
12
|
+
- `no_active_run` — no active embedded run was found;
|
|
13
|
+
- `rejected` — the runtime refused queueing.
|
|
12
14
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
acceptance and actual model consumption remain distinct.
|
|
15
|
+
LMA treats `steered` as provisional until the matching `session.message`
|
|
16
|
+
confirms transcript consumption. Any other outcome remains in LMA's durable
|
|
17
|
+
normal queue.
|
|
17
18
|
|
|
18
|
-
##
|
|
19
|
+
## Installation
|
|
19
20
|
|
|
20
|
-
|
|
21
|
-
requires `lma.steer`:
|
|
21
|
+
Realtime mid-run insertion requires this plugin:
|
|
22
22
|
|
|
23
23
|
```bash
|
|
24
24
|
lma install-steer-plugin
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
-
Restart the OpenClaw Gateway after installation.
|
|
28
|
-
|
|
27
|
+
Restart the OpenClaw Gateway after installation. LMA itself remains functional
|
|
28
|
+
without the plugin, but messages received during a run wait safely in the
|
|
29
|
+
normal queue.
|
|
29
30
|
|
|
30
|
-
##
|
|
31
|
+
## Gateway method
|
|
31
32
|
|
|
32
33
|
- method: `lma.steer`
|
|
33
34
|
- params: `{ sessionKey: string, text: string }`
|
|
34
35
|
- result: `{ status: "steered" | "no_active_run" | "rejected", sessionId?: string }`
|
|
35
|
-
|
|
36
|
-
Its synchronous `steered` result reflects immediate queue eligibility only; it
|
|
37
|
-
cannot prove later runtime consumption. This limitation is why current LMA uses
|
|
38
|
-
the public native steering path instead.
|