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 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 submits it via
105
- OpenClaw's public `chat.send` API with `queueMode: "steer"`. The RPC
106
- acknowledgment is provisional: LMA retains its durable pending trigger until a
107
- matching `session.message` proves transcript commitment/consumption, and only
108
- then switches Feishu from Typing to Get. Async `error`/`aborted` terminals are
109
- routed by run id, while unconsumed input remains available for normal queue
110
- fallback. This replaces the deprecated synchronous plugin primitive whose
111
- boolean could not represent later runtime rejection.
112
-
113
- OpenClaw 2026.8+ no longer requires the bundled `lma-steer` plugin. The
114
- `install-steer-plugin` command remains temporarily for older installations but
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] # legacy; not needed on OpenClaw 2026.8+
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 public `chat.send { queueMode: "steer" }` path. LMA
468
- keeps the durable trigger until `session.message` confirms consumption; this is
469
- also the signal for the visible Typing-to-Get transition.
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
- 执行中插入的消息改用公开的 `chat.send { queueMode: "steer" }`。RPC 成功返回只是暂时接受;LMA 会继续保留本地 pending trigger,只有匹配的 `session.message` 确认消息已写入转录并被消费后才清除,同时把飞书标记从 Typing 切换为 Get。异步 `error`/`aborted` 按 runId 投递,未消费的输入仍由普通队列兜底。这样不再依赖无法表达后续异步拒绝的旧同步插件原语。
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 legacy bundled lma-steer plugin. Current LMA uses OpenClaw's
210
- * public `chat.send { queueMode: "steer" }` API and does not call this plugin;
211
- * the command remains only for operators maintaining older LMA deployments.
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: current LMA releases use native chat.send queueMode=steer; this legacy plugin is not required on OpenClaw 2026.8+.");
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");
@@ -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
- if (provisionalTarget && steer.runId) {
988
- provisionalTarget.runId = steer.runId;
989
- this.bindRunDeliveryTarget(chatId, steer.runId, provisionalTarget);
990
- this.releaseRunDeliveryTargetAfter(chatId, steer.runId, 30 * 60_000);
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
- // Native steer was not accepted. Restore the previous run's routing
1003
- // target and keep the already-created trigger/reaction for normal drain.
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
- return en
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
- // Running/failed status owns timing; model attribution belongs to the final
2690
- // reply card so the two messages have clear, non-duplicated responsibilities.
2691
- const footer = en ? `⏱ ${view.elapsed}` : `⏱ 已用 ${view.elapsed}`;
2692
- elements.push({ tag: "markdown", content: `<font color='grey'>${footer}</font>` });
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 OpenClaw's public atomic steer path.
141
- * OpenClaw 2026.8 deprecated the synchronous plugin primitive previously used
142
- * by `lma-steer`: its boolean only represented immediate eligibility and could
143
- * still be rejected asynchronously. `chat.send queueMode=steer` owns admission
144
- * and transcript persistence. LMA still treats the RPC result as provisional;
145
- * the Feishu reaction flips to Get only after a matching `session.message`
146
- * confirms that the input was committed/consumed.
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";
@@ -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 OpenClaw's public atomic steer path.
1232
- * OpenClaw 2026.8 deprecated the synchronous plugin primitive previously used
1233
- * by `lma-steer`: its boolean only represented immediate eligibility and could
1234
- * still be rejected asynchronously. `chat.send queueMode=steer` owns admission
1235
- * and transcript persistence. LMA still treats the RPC result as provisional;
1236
- * the Feishu reaction flips to Get only after a matching `session.message`
1237
- * confirms that the input was committed/consumed.
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("chat.send", {
1243
- sessionKey: key,
1244
- message: text,
1245
- queueMode: "steer",
1246
- deliver: false,
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
- console.warn(`[OpenClaw] native steer failed for ${key.slice(-8)}: ${err?.message || String(err)}`);
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,6 +1,6 @@
1
1
  {
2
2
  "name": "openclaw-lark-multi-agent",
3
- "version": "1.4.6",
3
+ "version": "1.4.8",
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": {
@@ -1,38 +1,35 @@
1
- # lma-steer (legacy compatibility plugin)
1
+ # lma-steer
2
2
 
3
- This plugin is retained for older `openclaw-lark-multi-agent` deployments that
4
- called the private `lma.steer` Gateway method.
3
+ `lma-steer` provides observable mid-run insertion for
4
+ `openclaw-lark-multi-agent`.
5
5
 
6
- Current LMA releases on OpenClaw 2026.8+ do **not** use this plugin. They submit
7
- mid-run input through the public protocol API:
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
- ```text
10
- chat.send { queueMode: "steer" }
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
- The public path owns admission, while LMA retains its durable fallback trigger
14
- until a matching `session.message` confirms transcript consumption. Only then
15
- is the Feishu acknowledgement changed from Typing to Get, so provisional
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
- ## Legacy installation
19
+ ## Installation
19
20
 
20
- Only install this plugin when maintaining an older LMA release that explicitly
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. For new deployments, skip this
28
- step.
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
- ## Legacy method
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.