openclaw-lark-multi-agent 1.4.4 → 1.4.5

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openclaw-lark-multi-agent",
3
- "version": "1.4.4",
3
+ "version": "1.4.5",
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": {
@@ -23,7 +23,7 @@
23
23
  "@types/better-sqlite3": "^7.6.13",
24
24
  "@types/node": "^22.0.0",
25
25
  "@vitest/coverage-v8": "^4.1.5",
26
- "openclaw": "^2026.6.0",
26
+ "openclaw": "^2026.8.2",
27
27
  "tsx": "^4.19.0",
28
28
  "typescript": "^5.7.0",
29
29
  "vitest": "^4.1.5"
@@ -1,45 +1,38 @@
1
- # lma-steer
1
+ # lma-steer (legacy compatibility plugin)
2
2
 
3
- An OpenClaw plugin bundled with **openclaw-lark-multi-agent**. It registers a
4
- Gateway method `lma.steer` that the bridge uses to inject a message into an
5
- **active** OpenClaw run at the next tool-call boundary — so a user can nudge or
6
- correct a long-running task in real time instead of waiting for it to finish.
3
+ This plugin is retained for older `openclaw-lark-multi-agent` deployments that
4
+ called the private `lma.steer` Gateway method.
7
5
 
8
- ## Why a plugin
6
+ Current LMA releases on OpenClaw 2026.8+ do **not** use this plugin. They submit
7
+ mid-run input through the public protocol API:
9
8
 
10
- Native `chat.send` with `queue.mode=steer` only acks `{status:"started"}` — it
11
- cannot tell the bridge whether the message actually steered into the active run,
12
- was queued as a followup, or was rejected. This plugin uses the runtime
13
- primitives `resolveActiveEmbeddedRunSessionId` + `queueAgentHarnessMessage`,
14
- which expose that distinction, so the bridge can render an accurate Feishu
15
- reaction.
16
-
17
- ## Install
18
-
19
- From an installed `openclaw-lark-multi-agent`:
20
-
21
- ```bash
22
- openclaw-lark-multi-agent install-steer-plugin
9
+ ```text
10
+ chat.send { queueMode: "steer" }
23
11
  ```
24
12
 
25
- Then restart the OpenClaw gateway so it loads the plugin:
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.
26
17
 
27
- ```bash
28
- systemctl --user restart openclaw-gateway.service
29
- ```
18
+ ## Legacy installation
30
19
 
31
- Verify (an idle session returns `no_active_run`):
20
+ Only install this plugin when maintaining an older LMA release that explicitly
21
+ requires `lma.steer`:
32
22
 
33
23
  ```bash
34
- openclaw gateway call lma.steer --params '{"sessionKey":"test","text":"ping"}'
24
+ lma install-steer-plugin
35
25
  ```
36
26
 
37
- ## Gateway method
27
+ Restart the OpenClaw Gateway after installation. For new deployments, skip this
28
+ step.
38
29
 
39
- `lma.steer`
30
+ ## Legacy method
40
31
 
32
+ - method: `lma.steer`
41
33
  - params: `{ sessionKey: string, text: string }`
42
34
  - result: `{ status: "steered" | "no_active_run" | "rejected", sessionId?: string }`
43
- - `steered` — queued into the active run; seen at the next tool-call boundary
44
- - `no_active_run` — no active run; caller should send as a normal new message
45
- - `rejected` — active run refused the injection (e.g. compacting / not streaming)
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.
@@ -1,39 +0,0 @@
1
- export type ToolTrimResult = {
2
- ok: boolean;
3
- reason?: string;
4
- /** Bytes before / after, and line counts, for reporting. */
5
- bytesBefore?: number;
6
- bytesAfter?: number;
7
- linesBefore?: number;
8
- linesAfter?: number;
9
- removedToolResults?: number;
10
- strippedToolCalls?: number;
11
- removedEmptyAssistants?: number;
12
- keptRecentToolCalls?: number;
13
- backupPath?: string;
14
- };
15
- /**
16
- * Resolve the on-disk OpenClaw transcript file for a session.
17
- *
18
- * Session key looks like `agent:<agentId>:<label>` (e.g.
19
- * `agent:main:lma-claude-oc_xxx`). The transcript lives at
20
- * `<openclawHome>/agents/<agentId>/sessions/<sessionId>.jsonl`.
21
- */
22
- export declare function resolveSessionFilePath(sessionKey: string, sessionId: string, openclawHome?: string): string | null;
23
- /**
24
- * "Tool-trim" compaction: rewrite a session transcript in place, removing the
25
- * tool-call noise while keeping the conversation intact:
26
- * - drop every toolResult message entirely;
27
- * - strip the `toolCall` parts from assistant messages but KEEP their text
28
- * (the model's "thinking out loud while acting"); if an assistant message
29
- * had only tool calls and no text, drop the whole line;
30
- * - fix `stopReason: "toolUse"` -> "stop" on messages that no longer have a
31
- * tool call, so the loader is not confused.
32
- *
33
- * This does NOT call any model, so it works no matter how large the session is
34
- * (unlike OpenClaw's LLM-summary compaction, which itself fails to fit an
35
- * oversized session into the model). A backup is always written first.
36
- *
37
- * Typical reduction is ~70-90% for tool-heavy sessions.
38
- */
39
- export declare function toolTrimCompactFile(filePath: string, keepRecentToolCalls?: number): ToolTrimResult;
@@ -1,176 +0,0 @@
1
- import { existsSync, copyFileSync, readFileSync, writeFileSync, renameSync } from "fs";
2
- import { join } from "path";
3
- import { homedir } from "os";
4
- /**
5
- * Resolve the on-disk OpenClaw transcript file for a session.
6
- *
7
- * Session key looks like `agent:<agentId>:<label>` (e.g.
8
- * `agent:main:lma-claude-oc_xxx`). The transcript lives at
9
- * `<openclawHome>/agents/<agentId>/sessions/<sessionId>.jsonl`.
10
- */
11
- export function resolveSessionFilePath(sessionKey, sessionId, openclawHome) {
12
- if (!sessionId)
13
- return null;
14
- const home = openclawHome
15
- || process.env.OPENCLAW_HOME
16
- || process.env.OPENCLAW_CONFIG_DIR
17
- || join(homedir(), ".openclaw");
18
- // agentId is the second segment of agent:<agentId>:<label>; default to "main".
19
- const parts = sessionKey.split(":");
20
- const agentId = parts.length >= 3 && parts[0] === "agent" ? parts[1] : "main";
21
- const file = join(home, "agents", agentId, "sessions", `${sessionId}.jsonl`);
22
- return existsSync(file) ? file : null;
23
- }
24
- /**
25
- * "Tool-trim" compaction: rewrite a session transcript in place, removing the
26
- * tool-call noise while keeping the conversation intact:
27
- * - drop every toolResult message entirely;
28
- * - strip the `toolCall` parts from assistant messages but KEEP their text
29
- * (the model's "thinking out loud while acting"); if an assistant message
30
- * had only tool calls and no text, drop the whole line;
31
- * - fix `stopReason: "toolUse"` -> "stop" on messages that no longer have a
32
- * tool call, so the loader is not confused.
33
- *
34
- * This does NOT call any model, so it works no matter how large the session is
35
- * (unlike OpenClaw's LLM-summary compaction, which itself fails to fit an
36
- * oversized session into the model). A backup is always written first.
37
- *
38
- * Typical reduction is ~70-90% for tool-heavy sessions.
39
- */
40
- export function toolTrimCompactFile(filePath, keepRecentToolCalls = 3) {
41
- if (!existsSync(filePath))
42
- return { ok: false, reason: "transcript file not found" };
43
- const original = readFileSync(filePath, "utf8");
44
- const lines = original.split("\n");
45
- const bytesBefore = Buffer.byteLength(original, "utf8");
46
- // First pass: collect every toolCall id in order, so we can keep the most
47
- // recent N of them (and their matching toolResults) intact. Recent tool calls
48
- // are often still relevant to what the agent is doing next.
49
- const toolCallIds = [];
50
- for (const line of lines) {
51
- if (line.trim() === "")
52
- continue;
53
- let d;
54
- try {
55
- d = JSON.parse(line);
56
- }
57
- catch {
58
- continue;
59
- }
60
- if (d?.type !== "message")
61
- continue;
62
- const msg = d.message;
63
- if (msg?.role === "assistant" && Array.isArray(msg.content)) {
64
- for (const p of msg.content) {
65
- if (p && typeof p === "object" && p.type === "toolCall" && p.id)
66
- toolCallIds.push(String(p.id));
67
- }
68
- }
69
- }
70
- const keepN = Math.max(0, Math.floor(keepRecentToolCalls));
71
- const keepIds = new Set(keepN > 0 ? toolCallIds.slice(-keepN) : []);
72
- const out = [];
73
- let removedToolResults = 0;
74
- let strippedToolCalls = 0;
75
- let removedEmptyAssistants = 0;
76
- let keptRecentToolCalls = 0;
77
- let linesBefore = 0;
78
- for (const line of lines) {
79
- if (line.trim() === "") {
80
- continue;
81
- } // drop blank lines; we re-join with \n
82
- linesBefore++;
83
- let d;
84
- try {
85
- d = JSON.parse(line);
86
- }
87
- catch {
88
- out.push(line);
89
- continue;
90
- }
91
- if (d?.type !== "message") {
92
- out.push(line);
93
- continue;
94
- }
95
- const msg = d.message;
96
- const role = msg?.role;
97
- if (role === "toolResult") {
98
- // Keep the toolResult only if its toolCall is in the recent keep-set.
99
- if (msg?.toolCallId && keepIds.has(String(msg.toolCallId))) {
100
- out.push(line);
101
- continue;
102
- }
103
- removedToolResults++;
104
- continue;
105
- }
106
- if (role === "assistant" && Array.isArray(msg?.content)) {
107
- const content = msg.content;
108
- const hasToolCall = content.some((p) => p && typeof p === "object" && p.type === "toolCall");
109
- if (hasToolCall) {
110
- // Keep toolCalls that are in the recent keep-set; strip the rest.
111
- const kept = content.filter((p) => {
112
- if (!p || typeof p !== "object")
113
- return false;
114
- if (p.type !== "toolCall")
115
- return true;
116
- return p.id && keepIds.has(String(p.id));
117
- });
118
- const keptToolCall = kept.some((p) => p.type === "toolCall");
119
- const hasText = kept.some((p) => p.type === "text" && typeof p.text === "string" && p.text.trim() !== "");
120
- if (!keptToolCall && !hasText) {
121
- removedEmptyAssistants++;
122
- continue;
123
- }
124
- if (keptToolCall) {
125
- keptRecentToolCalls++;
126
- // stopReason stays toolUse since a toolCall remains.
127
- if (kept.length !== content.length)
128
- msg.content = kept;
129
- out.push(JSON.stringify(d));
130
- continue;
131
- }
132
- // All toolCalls stripped: keep text only, fix stopReason.
133
- msg.content = kept;
134
- if (msg.stopReason === "toolUse")
135
- msg.stopReason = "stop";
136
- strippedToolCalls++;
137
- out.push(JSON.stringify(d));
138
- continue;
139
- }
140
- }
141
- out.push(line);
142
- }
143
- // Safety: verify every output line is valid JSON before committing.
144
- for (const l of out) {
145
- try {
146
- JSON.parse(l);
147
- }
148
- catch {
149
- return { ok: false, reason: "produced invalid JSON; aborted" };
150
- }
151
- }
152
- if (removedToolResults === 0 && strippedToolCalls === 0 && removedEmptyAssistants === 0) {
153
- return { ok: true, reason: "no tool content to trim", bytesBefore, bytesAfter: bytesBefore, linesBefore, linesAfter: out.length, keptRecentToolCalls };
154
- }
155
- // Always back up first.
156
- const stamp = new Date().toISOString().replace(/[:.]/g, "-");
157
- const backupPath = `${filePath}.bak-tooltrim-${stamp}`;
158
- copyFileSync(filePath, backupPath);
159
- // Atomic write: temp file + rename.
160
- const body = out.join("\n") + "\n";
161
- const tmp = `${filePath}.tmp-tooltrim`;
162
- writeFileSync(tmp, body, "utf8");
163
- renameSync(tmp, filePath);
164
- return {
165
- ok: true,
166
- bytesBefore,
167
- bytesAfter: Buffer.byteLength(body, "utf8"),
168
- linesBefore,
169
- linesAfter: out.length,
170
- removedToolResults,
171
- strippedToolCalls,
172
- removedEmptyAssistants,
173
- keptRecentToolCalls,
174
- backupPath,
175
- };
176
- }