openclaw-lark-multi-agent 1.2.7 → 1.3.0
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 +38 -6
- package/dist/openclaw-client.d.ts +17 -0
- package/dist/openclaw-client.js +32 -0
- package/package.json +1 -1
package/dist/feishu-bot.js
CHANGED
|
@@ -895,11 +895,33 @@ export class FeishuBot {
|
|
|
895
895
|
const BUSY_TIMEOUT_MS = 1_800_000; // 30 minutes, matches collectReply safety timeout
|
|
896
896
|
const isBusy = busySince > 0 && (Date.now() - busySince) < BUSY_TIMEOUT_MS;
|
|
897
897
|
if (isBusy) {
|
|
898
|
-
//
|
|
898
|
+
// The agent is mid-run. Instead of only queuing to wait for the current
|
|
899
|
+
// run to finish, try to STEER this message into the active run so it is
|
|
900
|
+
// seen at the next tool-call boundary (lets the user nudge/correct a long
|
|
901
|
+
// run in real time). The lma-steer plugin reports whether it landed.
|
|
902
|
+
const sessionKeyForSteer = this.getSessionKey(chatId);
|
|
903
|
+
const steer = await this.openclawClient.steer(sessionKeyForSteer, cleanText).catch(() => ({ status: "unavailable" }));
|
|
904
|
+
if (steer.status === "steered") {
|
|
905
|
+
// Inserted into the active run. Reuse the existing reaction lifecycle:
|
|
906
|
+
// mark "Get" (received/inserted); the current run's completion will
|
|
907
|
+
// advance it to DONE together with the rest of the batch.
|
|
908
|
+
await this.addReaction(messageId, "Get").catch(() => { });
|
|
909
|
+
pending.push({ messageId, emoji: "Get", rowId: insertedId });
|
|
910
|
+
this.pendingAckMessages.set(chatId, pending);
|
|
911
|
+
console.log(`[${this.config.name}] Agent busy for ${chatId.slice(-8)}, STEERED into active run: "${cleanText.substring(0, 50)}..."`);
|
|
912
|
+
// The message is already in DB with a pending trigger. Clear that
|
|
913
|
+
// trigger so it is NOT re-run as a separate follow-up batch after the
|
|
914
|
+
// current run ends — it has already been injected into this run.
|
|
915
|
+
if (insertedId > 0)
|
|
916
|
+
this.store.clearPendingTrigger(this.config.name, chatId, insertedId);
|
|
917
|
+
return;
|
|
918
|
+
}
|
|
919
|
+
// Could not steer (no active run raced to idle, rejected, or plugin
|
|
920
|
+
// unavailable): fall back to the original behavior — queue and wait.
|
|
899
921
|
await this.addReaction(messageId, "Typing").catch(() => { });
|
|
900
922
|
pending.push({ messageId, emoji: "Typing", rowId: insertedId });
|
|
901
923
|
this.pendingAckMessages.set(chatId, pending);
|
|
902
|
-
console.log(`[${this.config.name}] Agent busy for ${chatId.slice(-8)}, queuing: "${cleanText.substring(0, 50)}..."`);
|
|
924
|
+
console.log(`[${this.config.name}] Agent busy for ${chatId.slice(-8)} (steer=${steer.status}), queuing: "${cleanText.substring(0, 50)}..."`);
|
|
903
925
|
return; // Message is in DB, will be picked up when agent finishes
|
|
904
926
|
}
|
|
905
927
|
if (busySince > 0) {
|
|
@@ -1119,6 +1141,13 @@ export class FeishuBot {
|
|
|
1119
1141
|
const isNativeCommandTrigger = firstIsNative;
|
|
1120
1142
|
const lastHuman = mergedTriggers[mergedTriggers.length - 1];
|
|
1121
1143
|
const triggerId = lastHuman.id || 0;
|
|
1144
|
+
// Snapshot the stop generation BEFORE the main run starts. If the user runs
|
|
1145
|
+
// /stop at any point during the main run, handleStopCommand bumps the epoch;
|
|
1146
|
+
// auto-retry compares against this baseline and skips retrying entirely. If
|
|
1147
|
+
// we snapshotted inside auto-retry instead, a /stop that landed during the
|
|
1148
|
+
// main run would already be baked into the baseline and go undetected —
|
|
1149
|
+
// that was the "stop then auto-retry anyway" bug.
|
|
1150
|
+
const runStartStopEpoch = this.stopEpoch.get(chatId) || 0;
|
|
1122
1151
|
const mergedContent = mergedTriggers.map((m) => m.content).join("\n");
|
|
1123
1152
|
const mergedTriggerIds = mergedTriggers.map((m) => m.id || 0).filter(Boolean);
|
|
1124
1153
|
const droppedTriggerIds = droppedMergedTriggers.map((m) => m.id || 0).filter(Boolean);
|
|
@@ -1267,6 +1296,7 @@ export class FeishuBot {
|
|
|
1267
1296
|
isNativeCommandTrigger,
|
|
1268
1297
|
initialReply: reply,
|
|
1269
1298
|
liveStatus,
|
|
1299
|
+
runStartStopEpoch,
|
|
1270
1300
|
});
|
|
1271
1301
|
const parsedReply = this.extractBridgeAttachments(reply);
|
|
1272
1302
|
const visibleReply = parsedReply.text;
|
|
@@ -1780,10 +1810,12 @@ export class FeishuBot {
|
|
|
1780
1810
|
if (!autoRetryEnabled() || isNativeCommandTrigger)
|
|
1781
1811
|
return reply;
|
|
1782
1812
|
const maxRetry = autoRetryMax();
|
|
1783
|
-
//
|
|
1784
|
-
//
|
|
1785
|
-
//
|
|
1786
|
-
|
|
1813
|
+
// Baseline stop generation. Prefer the epoch captured before the main run
|
|
1814
|
+
// (runStartStopEpoch) so a /stop that landed DURING the main run is still
|
|
1815
|
+
// seen here; fall back to the current epoch only if the caller did not pass
|
|
1816
|
+
// one. If the user hit /stop at any point from the main run onward, the
|
|
1817
|
+
// epoch is now past the baseline and we must not auto-retry at all.
|
|
1818
|
+
const startStopEpoch = params.runStartStopEpoch ?? (this.stopEpoch.get(chatId) || 0);
|
|
1787
1819
|
const wasStopped = () => (this.stopEpoch.get(chatId) || 0) !== startStopEpoch;
|
|
1788
1820
|
for (let attempt = 1; attempt <= maxRetry; attempt++) {
|
|
1789
1821
|
// /stop while we were looping: abandon retries and deliver what we have.
|
|
@@ -116,6 +116,23 @@ export declare class OpenClawClient {
|
|
|
116
116
|
message: string;
|
|
117
117
|
label?: string;
|
|
118
118
|
}): Promise<any>;
|
|
119
|
+
/**
|
|
120
|
+
* Steer a message into the ACTIVE run for a session via the lma-steer plugin's
|
|
121
|
+
* `lma.steer` gateway method. Unlike a plain new message, this injects the text
|
|
122
|
+
* at the next tool-call boundary of the run that is currently executing, so the
|
|
123
|
+
* user can nudge/correct a long run instead of waiting for it to finish.
|
|
124
|
+
*
|
|
125
|
+
* Returns a structured status so the bridge can render the right reaction:
|
|
126
|
+
* - "steered" -> queued into the active run (will be seen next boundary)
|
|
127
|
+
* - "no_active_run" -> no active run; caller should fall back to normal queueing
|
|
128
|
+
* - "rejected" -> active run refused the injection (compacting/not streaming)
|
|
129
|
+
* - "unavailable" -> the lma-steer plugin/method is not available (e.g. not
|
|
130
|
+
* installed or gateway not restarted); caller falls back.
|
|
131
|
+
*/
|
|
132
|
+
steer(sessionKey: string, text: string): Promise<{
|
|
133
|
+
status: "steered" | "no_active_run" | "rejected" | "unavailable";
|
|
134
|
+
sessionId?: string;
|
|
135
|
+
}>;
|
|
119
136
|
/**
|
|
120
137
|
* Get session info (model, tokens, etc.) for status display.
|
|
121
138
|
*/
|
package/dist/openclaw-client.js
CHANGED
|
@@ -920,6 +920,38 @@ export class OpenClawClient {
|
|
|
920
920
|
async injectAssistantMessage(params) {
|
|
921
921
|
return this.rpc("chat.inject", params, 10000);
|
|
922
922
|
}
|
|
923
|
+
/**
|
|
924
|
+
* Steer a message into the ACTIVE run for a session via the lma-steer plugin's
|
|
925
|
+
* `lma.steer` gateway method. Unlike a plain new message, this injects the text
|
|
926
|
+
* at the next tool-call boundary of the run that is currently executing, so the
|
|
927
|
+
* user can nudge/correct a long run instead of waiting for it to finish.
|
|
928
|
+
*
|
|
929
|
+
* Returns a structured status so the bridge can render the right reaction:
|
|
930
|
+
* - "steered" -> queued into the active run (will be seen next boundary)
|
|
931
|
+
* - "no_active_run" -> no active run; caller should fall back to normal queueing
|
|
932
|
+
* - "rejected" -> active run refused the injection (compacting/not streaming)
|
|
933
|
+
* - "unavailable" -> the lma-steer plugin/method is not available (e.g. not
|
|
934
|
+
* installed or gateway not restarted); caller falls back.
|
|
935
|
+
*/
|
|
936
|
+
async steer(sessionKey, text) {
|
|
937
|
+
try {
|
|
938
|
+
const res = await this.rpc("lma.steer", { sessionKey, text }, 10000);
|
|
939
|
+
const status = res?.status;
|
|
940
|
+
if (status === "steered" || status === "no_active_run" || status === "rejected") {
|
|
941
|
+
return { status, sessionId: typeof res?.sessionId === "string" ? res.sessionId : undefined };
|
|
942
|
+
}
|
|
943
|
+
return { status: "unavailable" };
|
|
944
|
+
}
|
|
945
|
+
catch (err) {
|
|
946
|
+
// Unknown method / plugin missing / transient RPC error -> caller falls back
|
|
947
|
+
// to normal queueing. Never throw into the message path.
|
|
948
|
+
const msg = err?.message || String(err);
|
|
949
|
+
if (!/unknown method/i.test(msg)) {
|
|
950
|
+
console.warn(`[OpenClaw] lma.steer failed for ${sessionKey.slice(-8)}: ${msg}`);
|
|
951
|
+
}
|
|
952
|
+
return { status: "unavailable" };
|
|
953
|
+
}
|
|
954
|
+
}
|
|
923
955
|
/**
|
|
924
956
|
* Get session info (model, tokens, etc.) for status display.
|
|
925
957
|
*/
|