openclaw-lark-multi-agent 1.4.13 → 1.5.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/compact-progress.d.ts +3 -1
- package/dist/compact-progress.js +6 -0
- package/dist/config.d.ts +2 -0
- package/dist/feishu-bot.d.ts +12 -11
- package/dist/feishu-bot.js +100 -54
- package/dist/live-status.d.ts +5 -0
- package/dist/live-status.js +2 -0
- package/dist/openclaw-client.d.ts +8 -0
- package/dist/openclaw-client.js +57 -2
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Coarse phase of the compaction flow, drives the card's primary line. */
|
|
2
|
-
export type CompactPhase = "native" | "transcript-trim";
|
|
2
|
+
export type CompactPhase = "native" | "transcript-trim" | "trim";
|
|
3
3
|
/** Terminal outcome used by the renderer to pick color/emoji. */
|
|
4
4
|
export type CompactState = "running" | "done" | "failed" | "noop";
|
|
5
5
|
/** Structured snapshot the renderer turns into a Feishu interactive card. */
|
|
@@ -50,6 +50,8 @@ export declare class CompactProgressController {
|
|
|
50
50
|
get id(): string | undefined;
|
|
51
51
|
/** Begin the flow; schedules lazy card creation after delayMs. */
|
|
52
52
|
start(): void;
|
|
53
|
+
/** Switch straight into explicit transcript-trim mode (user-requested trim). */
|
|
54
|
+
toTrim(): void;
|
|
53
55
|
/** Native compaction gave up; switch to Gateway-owned transcript-tail trim. */
|
|
54
56
|
toTranscriptTrim(): Promise<void>;
|
|
55
57
|
/** Finished successfully; patch the card to the done state with a detail line. */
|
package/dist/compact-progress.js
CHANGED
|
@@ -45,6 +45,12 @@ export class CompactProgressController {
|
|
|
45
45
|
}, this.opts.delayMs ?? DEFAULT_DELAY_MS);
|
|
46
46
|
this.createTimer.unref?.();
|
|
47
47
|
}
|
|
48
|
+
/** Switch straight into explicit transcript-trim mode (user-requested trim). */
|
|
49
|
+
toTrim() {
|
|
50
|
+
if (this.finalized || this.phase === "trim" || this.phase === "transcript-trim")
|
|
51
|
+
return;
|
|
52
|
+
this.phase = "trim";
|
|
53
|
+
}
|
|
48
54
|
/** Native compaction gave up; switch to Gateway-owned transcript-tail trim. */
|
|
49
55
|
async toTranscriptTrim() {
|
|
50
56
|
if (this.finalized || this.phase === "transcript-trim")
|
package/dist/config.d.ts
CHANGED
package/dist/feishu-bot.d.ts
CHANGED
|
@@ -219,6 +219,10 @@ export declare class FeishuBot {
|
|
|
219
219
|
private isBotAvailableInChat;
|
|
220
220
|
private getDiscussionParticipants;
|
|
221
221
|
private getChairmanParticipant;
|
|
222
|
+
/** Resolve the session's current thinking label for the live-status footer.
|
|
223
|
+
* Prefers a live session override (set via //think / sessions.patch), falls
|
|
224
|
+
* back to the session's default, then the bot's config-level thinking. */
|
|
225
|
+
private fetchThinkingLabel;
|
|
222
226
|
private asDiscussionParticipant;
|
|
223
227
|
private runDiscussionTurn;
|
|
224
228
|
private buildLiveStatusCard;
|
|
@@ -316,14 +320,15 @@ export declare class FeishuBot {
|
|
|
316
320
|
* Handle /compact command: compress session context.
|
|
317
321
|
*/
|
|
318
322
|
/**
|
|
319
|
-
* Compact a session
|
|
320
|
-
*
|
|
321
|
-
* 2. If summarization cannot run, ask the same RPC to retain only a bounded
|
|
322
|
-
* transcript tail via `maxLines`. This works with legacy JSONL and the
|
|
323
|
-
* SQLite session store introduced in OpenClaw 2026.8.x, and preserves
|
|
324
|
-
* OpenClaw's write locks, transcript invariants, and migration ownership.
|
|
323
|
+
* Compact a session via Gateway-owned RPCs. LMA deliberately never opens or
|
|
324
|
+
* rewrites OpenClaw transcript storage.
|
|
325
325
|
*
|
|
326
|
-
*
|
|
326
|
+
* Modes (kept strictly separate so the flows are never mixed):
|
|
327
|
+
* - "trim" (default): `sessions.compact` with `maxLines` — permanently keeps
|
|
328
|
+
* only the last N transcript events. Model-free, fast, never times out on
|
|
329
|
+
* oversized sessions.
|
|
330
|
+
* - "semantic": LLM summarization via the same RPC; on failure falls back to
|
|
331
|
+
* a trim so the session is still reclaimed.
|
|
327
332
|
*/
|
|
328
333
|
private compactWithFallback;
|
|
329
334
|
private handleCompactCommand;
|
|
@@ -344,9 +349,6 @@ export declare class FeishuBot {
|
|
|
344
349
|
* Called once per chat on first message.
|
|
345
350
|
*/
|
|
346
351
|
private fetchAndCacheChatInfo;
|
|
347
|
-
/**
|
|
348
|
-
* Send a model-drift notification to the affected chat.
|
|
349
|
-
*/
|
|
350
352
|
private hydrateInlineImageKeys;
|
|
351
353
|
private hydrateFeishuDocsInMessage;
|
|
352
354
|
private extractFeishuDocRefs;
|
|
@@ -365,5 +367,4 @@ export declare class FeishuBot {
|
|
|
365
367
|
* Extract text content from a rich text (post) message.
|
|
366
368
|
*/
|
|
367
369
|
private extractPostText;
|
|
368
|
-
private notifyModelDrift;
|
|
369
370
|
}
|
package/dist/feishu-bot.js
CHANGED
|
@@ -44,12 +44,12 @@ function steerInjectionPrefix(en) {
|
|
|
44
44
|
|| "[New message inserted by the user mid-task; please handle it together with the current task]";
|
|
45
45
|
return en ? enText : zh;
|
|
46
46
|
}
|
|
47
|
-
//
|
|
48
|
-
//
|
|
49
|
-
//
|
|
47
|
+
// Default transcript-tail trim depth for /compact. Tool-heavy group sessions can
|
|
48
|
+
// easily carry 300+ events of pure tool noise, so keep a generous tail: the
|
|
49
|
+
// Gateway truncates to the last N transcript events. Env override stays.
|
|
50
50
|
function compactFallbackMaxLines() {
|
|
51
|
-
const value = Number(process.env.OPENCLAW_LARK_MULTI_AGENT_COMPACT_FALLBACK_MAX_LINES ||
|
|
52
|
-
return Number.isFinite(value) ? Math.max(20, Math.floor(value)) :
|
|
51
|
+
const value = Number(process.env.OPENCLAW_LARK_MULTI_AGENT_COMPACT_FALLBACK_MAX_LINES || 800);
|
|
52
|
+
return Number.isFinite(value) ? Math.max(20, Math.floor(value)) : 800;
|
|
53
53
|
}
|
|
54
54
|
// After a reply is delivered, if context usage reaches this percent, send the
|
|
55
55
|
// user a one-time alert to compact. 0 disables the alert.
|
|
@@ -212,7 +212,6 @@ export class FeishuBot {
|
|
|
212
212
|
const corrected = await this.openclawClient.ensureModel(sessionKey, this.config.model);
|
|
213
213
|
if (corrected) {
|
|
214
214
|
console.log(`[${this.config.name}] Model auto-corrected to ${this.config.model}`);
|
|
215
|
-
await this.notifyModelDrift(chatId, sessionKey);
|
|
216
215
|
}
|
|
217
216
|
return sessionKey;
|
|
218
217
|
}
|
|
@@ -225,7 +224,6 @@ export class FeishuBot {
|
|
|
225
224
|
const corrected = await this.openclawClient.ensureModel(sessionKey, this.config.model);
|
|
226
225
|
if (corrected) {
|
|
227
226
|
console.log(`[${this.config.name}] Model auto-corrected to ${this.config.model}`);
|
|
228
|
-
await this.notifyModelDrift(chatId, sessionKey);
|
|
229
227
|
}
|
|
230
228
|
}
|
|
231
229
|
else {
|
|
@@ -683,7 +681,7 @@ export class FeishuBot {
|
|
|
683
681
|
`━━━━━━━━━━━━━━━━━━`,
|
|
684
682
|
`桥接层命令(单斜杠,由 openclaw-lark-multi-agent 本地处理)`,
|
|
685
683
|
`📊 /status — 查看当前模型、Token 用量、Session 状态`,
|
|
686
|
-
`🧹 /compact —
|
|
684
|
+
`🧹 /compact [N|maxLines=N|semantic] — 裁剪转录尾部(默认,保留最近事件);N 指定保留条数;semantic 走 LLM 总结`,
|
|
687
685
|
`🔄 /reset — 重置当前 bot 的 OpenClaw session`,
|
|
688
686
|
`⏹️ /stop — 强制停止当前 bot 在本聊天的卡死 run,解锁队列`,
|
|
689
687
|
`🔊 /verbose — 开关当前聊天里的 Tool Call 显示`,
|
|
@@ -729,7 +727,8 @@ export class FeishuBot {
|
|
|
729
727
|
}
|
|
730
728
|
if (commandName === "/compact") {
|
|
731
729
|
await this.ensureSession(chatId);
|
|
732
|
-
|
|
730
|
+
const compactArgs = cleanText.trim().split(/\s+/).slice(1).join(" ").trim();
|
|
731
|
+
await this.handleCompactCommand(chatId, messageId, compactArgs);
|
|
733
732
|
markCommandSynced();
|
|
734
733
|
return;
|
|
735
734
|
}
|
|
@@ -1360,6 +1359,7 @@ export class FeishuBot {
|
|
|
1360
1359
|
}, {
|
|
1361
1360
|
botName: this.config.name,
|
|
1362
1361
|
model: this.config.model,
|
|
1362
|
+
thinkingLevel: await this.fetchThinkingLabel(sessionKey),
|
|
1363
1363
|
locale: this.isEn(chatId) ? "en" : "zh",
|
|
1364
1364
|
})
|
|
1365
1365
|
: undefined;
|
|
@@ -1888,7 +1888,12 @@ export class FeishuBot {
|
|
|
1888
1888
|
const core = en
|
|
1889
1889
|
? `${emoji} ${meta.toolCalls} tool call${meta.toolCalls === 1 ? "" : "s"} · ⏱ ${meta.elapsed}`
|
|
1890
1890
|
: `${emoji} 累计${meta.toolCalls} 次工具调用 · ⏱ 耗时${meta.elapsed}`;
|
|
1891
|
-
|
|
1891
|
+
if (!meta.model?.trim())
|
|
1892
|
+
return core;
|
|
1893
|
+
const modelLabel = meta.thinkingLevel?.trim()
|
|
1894
|
+
? `${meta.model.trim()} · ${meta.thinkingLevel.trim()}`
|
|
1895
|
+
: meta.model.trim();
|
|
1896
|
+
return `${core} · 🧠 ${this.escapeCardText(modelLabel)}`;
|
|
1892
1897
|
}
|
|
1893
1898
|
/** Best-effort terminal cleanup for status-only rows and legacy v1.4.3
|
|
1894
1899
|
* outbox entries that once targeted the live-status card. */
|
|
@@ -1898,6 +1903,7 @@ export class FeishuBot {
|
|
|
1898
1903
|
lines: [],
|
|
1899
1904
|
elapsed: meta.elapsed,
|
|
1900
1905
|
model: meta.model,
|
|
1906
|
+
thinkingLevel: meta.thinkingLevel,
|
|
1901
1907
|
toolCalls: meta.toolCalls,
|
|
1902
1908
|
noReply: Boolean(meta.noReply),
|
|
1903
1909
|
state: "done",
|
|
@@ -2783,6 +2789,24 @@ export class FeishuBot {
|
|
|
2783
2789
|
const bot = Array.from(FeishuBot.allBots.values()).find((candidate) => candidate.store === this.store && candidate.config.name === chairman && this.isBotAvailableInChat(candidate, chatId));
|
|
2784
2790
|
return bot ? this.asDiscussionParticipant(bot, chatId) : undefined;
|
|
2785
2791
|
}
|
|
2792
|
+
/** Resolve the session's current thinking label for the live-status footer.
|
|
2793
|
+
* Prefers a live session override (set via //think / sessions.patch), falls
|
|
2794
|
+
* back to the session's default, then the bot's config-level thinking. */
|
|
2795
|
+
async fetchThinkingLabel(sessionKey) {
|
|
2796
|
+
try {
|
|
2797
|
+
const info = await this.openclawClient.getSessionInfo(sessionKey);
|
|
2798
|
+
const s = info?.session;
|
|
2799
|
+
const label = (typeof s?.thinkingLevel === "string" && s.thinkingLevel.trim())
|
|
2800
|
+
|| (typeof s?.thinkingDefault === "string" && s.thinkingDefault.trim())
|
|
2801
|
+
|| (typeof this.config.thinking === "string" && this.config.thinking.trim())
|
|
2802
|
+
|| "";
|
|
2803
|
+
return label || undefined;
|
|
2804
|
+
}
|
|
2805
|
+
catch {
|
|
2806
|
+
const cfg = typeof this.config.thinking === "string" ? this.config.thinking.trim() : "";
|
|
2807
|
+
return cfg || undefined;
|
|
2808
|
+
}
|
|
2809
|
+
}
|
|
2786
2810
|
asDiscussionParticipant(bot, chatId) {
|
|
2787
2811
|
return {
|
|
2788
2812
|
name: bot.config.name,
|
|
@@ -2801,6 +2825,7 @@ export class FeishuBot {
|
|
|
2801
2825
|
}, {
|
|
2802
2826
|
botName: this.config.name,
|
|
2803
2827
|
model: this.config.model,
|
|
2828
|
+
thinkingLevel: await this.fetchThinkingLabel(sessionKey),
|
|
2804
2829
|
locale: this.isEn(chatId) ? "en" : "zh",
|
|
2805
2830
|
})
|
|
2806
2831
|
: undefined;
|
|
@@ -2900,6 +2925,7 @@ export class FeishuBot {
|
|
|
2900
2925
|
toolCalls: view.toolCalls,
|
|
2901
2926
|
elapsed: view.elapsed,
|
|
2902
2927
|
model: view.model,
|
|
2928
|
+
thinkingLevel: view.thinkingLevel,
|
|
2903
2929
|
locale: en ? "en" : "zh",
|
|
2904
2930
|
}, statusEmoji);
|
|
2905
2931
|
return {
|
|
@@ -2935,8 +2961,12 @@ export class FeishuBot {
|
|
|
2935
2961
|
// Keep the active model visible while the run is in progress or failed.
|
|
2936
2962
|
// The final reply also keeps model attribution so it remains self-contained.
|
|
2937
2963
|
const footerBits = [en ? `⏱ ${view.elapsed}` : `⏱ 已用 ${view.elapsed}`];
|
|
2938
|
-
if (view.model?.trim())
|
|
2939
|
-
|
|
2964
|
+
if (view.model?.trim()) {
|
|
2965
|
+
const modelBit = view.thinkingLevel?.trim()
|
|
2966
|
+
? `🧠 ${this.escapeCardText(view.model.trim())} · ${this.escapeCardText(view.thinkingLevel.trim())}`
|
|
2967
|
+
: `🧠 ${this.escapeCardText(view.model.trim())}`;
|
|
2968
|
+
footerBits.push(modelBit);
|
|
2969
|
+
}
|
|
2940
2970
|
elements.push({ tag: "markdown", content: `<font color='grey'>${footerBits.join(" · ")}</font>` });
|
|
2941
2971
|
const template = view.state === "failed" ? "orange" : "blue";
|
|
2942
2972
|
return {
|
|
@@ -3023,7 +3053,9 @@ export class FeishuBot {
|
|
|
3023
3053
|
// Running: phase line + ticking elapsed footer.
|
|
3024
3054
|
const phaseLine = view.phase === "transcript-trim"
|
|
3025
3055
|
? (en ? "🧹 Semantic compaction unavailable — trimming transcript safely…" : "🧹 语义压缩不可用,正在安全裁剪转录…")
|
|
3026
|
-
:
|
|
3056
|
+
: view.phase === "trim"
|
|
3057
|
+
? (en ? "🧹 Trimming transcript tail (keeping the most recent events)…" : "🧹 正在裁剪转录尾部(保留最近的事件)…")
|
|
3058
|
+
: (en ? "🧹 Compacting session…" : "🧹 正在压缩 session…");
|
|
3027
3059
|
const footer = en ? `⏱ ${view.elapsed}` : `⏱ 已用 ${view.elapsed}`;
|
|
3028
3060
|
return {
|
|
3029
3061
|
schema: "2.0",
|
|
@@ -3800,16 +3832,37 @@ export class FeishuBot {
|
|
|
3800
3832
|
* Handle /compact command: compress session context.
|
|
3801
3833
|
*/
|
|
3802
3834
|
/**
|
|
3803
|
-
* Compact a session
|
|
3804
|
-
*
|
|
3805
|
-
* 2. If summarization cannot run, ask the same RPC to retain only a bounded
|
|
3806
|
-
* transcript tail via `maxLines`. This works with legacy JSONL and the
|
|
3807
|
-
* SQLite session store introduced in OpenClaw 2026.8.x, and preserves
|
|
3808
|
-
* OpenClaw's write locks, transcript invariants, and migration ownership.
|
|
3835
|
+
* Compact a session via Gateway-owned RPCs. LMA deliberately never opens or
|
|
3836
|
+
* rewrites OpenClaw transcript storage.
|
|
3809
3837
|
*
|
|
3810
|
-
*
|
|
3838
|
+
* Modes (kept strictly separate so the flows are never mixed):
|
|
3839
|
+
* - "trim" (default): `sessions.compact` with `maxLines` — permanently keeps
|
|
3840
|
+
* only the last N transcript events. Model-free, fast, never times out on
|
|
3841
|
+
* oversized sessions.
|
|
3842
|
+
* - "semantic": LLM summarization via the same RPC; on failure falls back to
|
|
3843
|
+
* a trim so the session is still reclaimed.
|
|
3811
3844
|
*/
|
|
3812
|
-
async compactWithFallback(sessionKey, onPhase) {
|
|
3845
|
+
async compactWithFallback(sessionKey, onPhase, opts = {}) {
|
|
3846
|
+
const mode = opts.mode === "semantic" ? "semantic" : "trim";
|
|
3847
|
+
const trimMaxLines = Number.isFinite(opts.maxLines) && opts.maxLines >= 20
|
|
3848
|
+
? Math.floor(opts.maxLines)
|
|
3849
|
+
: compactFallbackMaxLines();
|
|
3850
|
+
if (mode === "trim") {
|
|
3851
|
+
onPhase?.("trim");
|
|
3852
|
+
try {
|
|
3853
|
+
const trimmed = await this.openclawClient.compactSession(sessionKey, { maxLines: trimMaxLines });
|
|
3854
|
+
if (trimmed?.compacted === true) {
|
|
3855
|
+
const kept = typeof trimmed?.kept === "number" ? trimmed.kept : trimMaxLines;
|
|
3856
|
+
return { compacted: true, method: "transcript-trim", detail: `kept ${kept} transcript events` };
|
|
3857
|
+
}
|
|
3858
|
+
const trimReason = typeof trimmed?.reason === "string" ? trimmed.reason : undefined;
|
|
3859
|
+
return { compacted: false, method: "none", reason: trimReason || "transcript trim made no change" };
|
|
3860
|
+
}
|
|
3861
|
+
catch (err) {
|
|
3862
|
+
return { compacted: false, method: "none", reason: err.message };
|
|
3863
|
+
}
|
|
3864
|
+
}
|
|
3865
|
+
// semantic mode: LLM summarize first, trim as fallback.
|
|
3813
3866
|
let nativeReason;
|
|
3814
3867
|
try {
|
|
3815
3868
|
const res = await this.openclawClient.compactSession(sessionKey);
|
|
@@ -3821,11 +3874,10 @@ export class FeishuBot {
|
|
|
3821
3874
|
nativeReason = err.message;
|
|
3822
3875
|
}
|
|
3823
3876
|
onPhase?.("transcript-trim");
|
|
3824
|
-
const maxLines = compactFallbackMaxLines();
|
|
3825
3877
|
try {
|
|
3826
|
-
const trimmed = await this.openclawClient.compactSession(sessionKey, { maxLines });
|
|
3878
|
+
const trimmed = await this.openclawClient.compactSession(sessionKey, { maxLines: trimMaxLines });
|
|
3827
3879
|
if (trimmed?.compacted === true) {
|
|
3828
|
-
const kept = typeof trimmed?.kept === "number" ? trimmed.kept :
|
|
3880
|
+
const kept = typeof trimmed?.kept === "number" ? trimmed.kept : trimMaxLines;
|
|
3829
3881
|
return {
|
|
3830
3882
|
compacted: true,
|
|
3831
3883
|
method: "transcript-trim",
|
|
@@ -3850,25 +3902,42 @@ export class FeishuBot {
|
|
|
3850
3902
|
};
|
|
3851
3903
|
}
|
|
3852
3904
|
}
|
|
3853
|
-
async handleCompactCommand(chatId, messageId) {
|
|
3905
|
+
async handleCompactCommand(chatId, messageId, args = "") {
|
|
3854
3906
|
const sessionKey = this.getSessionKey(chatId);
|
|
3855
3907
|
const en = this.isEn(chatId);
|
|
3856
|
-
//
|
|
3857
|
-
//
|
|
3858
|
-
//
|
|
3859
|
-
//
|
|
3860
|
-
//
|
|
3908
|
+
// Argument parsing — modes are strictly separated:
|
|
3909
|
+
// /compact → transcript tail trim (default, model-free)
|
|
3910
|
+
// /compact semantic → LLM summarization (trim fallback on failure)
|
|
3911
|
+
// /compact 500 → tail trim keeping the last 500 events
|
|
3912
|
+
// /compact maxLines=500 → same as above
|
|
3913
|
+
const argTokens = args.split(/\s+/).filter(Boolean);
|
|
3914
|
+
let mode = "trim";
|
|
3915
|
+
let maxLines;
|
|
3916
|
+
for (const tok of argTokens) {
|
|
3917
|
+
const semanticMatch = /^(semantic|summarize|llm)$/i.exec(tok);
|
|
3918
|
+
if (semanticMatch) {
|
|
3919
|
+
mode = "semantic";
|
|
3920
|
+
continue;
|
|
3921
|
+
}
|
|
3922
|
+
const linesMatch = /^(?:maxlines|max_lines)[=:](\d{2,})$/i.exec(tok) || /^(\d{2,})$/.exec(tok);
|
|
3923
|
+
if (linesMatch) {
|
|
3924
|
+
maxLines = Number(linesMatch[1] || linesMatch[2]);
|
|
3925
|
+
continue;
|
|
3926
|
+
}
|
|
3927
|
+
}
|
|
3861
3928
|
const progress = new CompactProgressController({
|
|
3862
3929
|
create: (view) => this.sendOrdered(chatId, () => this.replyCompactCard(messageId, view, chatId)),
|
|
3863
3930
|
edit: (id, view) => this.sendOrdered(chatId, () => this.patchCompactCard(id, view, chatId)),
|
|
3864
3931
|
warn: (msg, err) => console.warn(`[${this.config.name}] ${msg}`, err instanceof Error ? err.message : err),
|
|
3865
3932
|
}, { locale: en ? "en" : "zh" });
|
|
3933
|
+
if (mode === "trim")
|
|
3934
|
+
progress.toTrim();
|
|
3866
3935
|
progress.start();
|
|
3867
3936
|
try {
|
|
3868
3937
|
const r = await this.compactWithFallback(sessionKey, (phase) => {
|
|
3869
3938
|
if (phase === "transcript-trim")
|
|
3870
3939
|
void progress.toTranscriptTrim().catch(() => { });
|
|
3871
|
-
});
|
|
3940
|
+
}, { mode, maxLines });
|
|
3872
3941
|
if (r.compacted) {
|
|
3873
3942
|
// If a card was shown, patch it in place; otherwise (fast finish, no card)
|
|
3874
3943
|
// fall back to a normal reply so the user still gets confirmation.
|
|
@@ -4031,9 +4100,6 @@ export class FeishuBot {
|
|
|
4031
4100
|
console.warn(`[${this.config.name}] Failed to fetch chat info:`, err.message);
|
|
4032
4101
|
}
|
|
4033
4102
|
}
|
|
4034
|
-
/**
|
|
4035
|
-
* Send a model-drift notification to the affected chat.
|
|
4036
|
-
*/
|
|
4037
4103
|
async hydrateInlineImageKeys(text, messageId) {
|
|
4038
4104
|
const imageKeyPattern = /\[Image: (img_[^\]\n]+)\]/g;
|
|
4039
4105
|
const replacements = [];
|
|
@@ -4261,24 +4327,4 @@ export class FeishuBot {
|
|
|
4261
4327
|
}
|
|
4262
4328
|
return this.cleanMentions(parts.join(" "));
|
|
4263
4329
|
}
|
|
4264
|
-
async notifyModelDrift(chatId, _sessionKey) {
|
|
4265
|
-
try {
|
|
4266
|
-
const chatInfo = this.store.getChatInfo(chatId);
|
|
4267
|
-
const chatLabel = chatInfo?.chatName || chatId.slice(-8);
|
|
4268
|
-
await this.client.im.message.create({
|
|
4269
|
-
params: { receive_id_type: "chat_id" },
|
|
4270
|
-
data: {
|
|
4271
|
-
receive_id: chatId,
|
|
4272
|
-
content: JSON.stringify({
|
|
4273
|
-
text: `⚠️ 模型漂移已自动纠正\n期望: ${this.config.model}\n已恢复`,
|
|
4274
|
-
}),
|
|
4275
|
-
msg_type: "text",
|
|
4276
|
-
},
|
|
4277
|
-
});
|
|
4278
|
-
console.log(`[${this.config.name}] Drift notification sent to ${chatLabel}`);
|
|
4279
|
-
}
|
|
4280
|
-
catch (err) {
|
|
4281
|
-
console.warn(`[${this.config.name}] Failed to notify drift:`, err.message);
|
|
4282
|
-
}
|
|
4283
|
-
}
|
|
4284
4330
|
}
|
package/dist/live-status.d.ts
CHANGED
|
@@ -16,6 +16,8 @@ export type LiveStatusView = {
|
|
|
16
16
|
elapsed: string;
|
|
17
17
|
/** Footer: model name, e.g. "phgeek-gw/claude-opus-4.8". */
|
|
18
18
|
model?: string;
|
|
19
|
+
/** Footer: current thinking level label, e.g. "high" / "adaptive". */
|
|
20
|
+
thinkingLevel?: string;
|
|
19
21
|
/** Total tool calls in the run (for the compact finished summary). */
|
|
20
22
|
toolCalls: number;
|
|
21
23
|
/** True when finished via noReply(): the model produced no user-visible reply. */
|
|
@@ -29,6 +31,7 @@ export type LiveStatusFinalMeta = {
|
|
|
29
31
|
toolCalls: number;
|
|
30
32
|
elapsed: string;
|
|
31
33
|
model?: string;
|
|
34
|
+
thinkingLevel?: string;
|
|
32
35
|
locale: "zh" | "en";
|
|
33
36
|
noReply?: boolean;
|
|
34
37
|
};
|
|
@@ -46,6 +49,8 @@ export type LiveStatusOptions = {
|
|
|
46
49
|
maxChars?: number;
|
|
47
50
|
/** Model name shown in the footer. */
|
|
48
51
|
model?: string;
|
|
52
|
+
/** Thinking level label shown after the model name in the footer. */
|
|
53
|
+
thinkingLevel?: string;
|
|
49
54
|
/** Number of recent activity lines kept in the content area. */
|
|
50
55
|
historySize?: number;
|
|
51
56
|
/** Footer/auto-refresh cadence in ms. */
|
package/dist/live-status.js
CHANGED
|
@@ -172,6 +172,7 @@ export class LiveStatusController {
|
|
|
172
172
|
toolCalls: this.toolCallCount,
|
|
173
173
|
elapsed: this.formatElapsed(),
|
|
174
174
|
model: this.opts.model,
|
|
175
|
+
thinkingLevel: this.opts.thinkingLevel,
|
|
175
176
|
locale: this.opts.locale === "en" ? "en" : "zh",
|
|
176
177
|
noReply,
|
|
177
178
|
};
|
|
@@ -403,6 +404,7 @@ export class LiveStatusController {
|
|
|
403
404
|
lines,
|
|
404
405
|
elapsed: this.formatElapsed(),
|
|
405
406
|
model: this.opts.model,
|
|
407
|
+
thinkingLevel: this.opts.thinkingLevel,
|
|
406
408
|
toolCalls: this.toolCallCount,
|
|
407
409
|
noReply: this.noReplyResult,
|
|
408
410
|
state: this.state,
|
|
@@ -143,6 +143,14 @@ export declare class OpenClawClient {
|
|
|
143
143
|
model?: string;
|
|
144
144
|
label?: string;
|
|
145
145
|
}): Promise<any>;
|
|
146
|
+
/** True when an RPC error is the Gateway's "session is archived" refusal. */
|
|
147
|
+
isArchivedSessionError(err: unknown): boolean;
|
|
148
|
+
/**
|
|
149
|
+
* Restore an archived session (e.g. auto-archived by the Gateway's
|
|
150
|
+
* active-session-cap) so new work can start. sessions.patch requires
|
|
151
|
+
* expectedSessionId, so describe first, then patch archived=false.
|
|
152
|
+
*/
|
|
153
|
+
unarchiveSession(key: string): Promise<boolean>;
|
|
146
154
|
getSessionStatus(key: string): Promise<any>;
|
|
147
155
|
injectAssistantMessage(params: {
|
|
148
156
|
sessionKey: string;
|
package/dist/openclaw-client.js
CHANGED
|
@@ -1545,6 +1545,34 @@ export class OpenClawClient {
|
|
|
1545
1545
|
async patchSession(params) {
|
|
1546
1546
|
return this.rpc("sessions.patch", { ...params, key: this.canonicalSessionKey(params.key) }, 10000);
|
|
1547
1547
|
}
|
|
1548
|
+
/** True when an RPC error is the Gateway's "session is archived" refusal. */
|
|
1549
|
+
isArchivedSessionError(err) {
|
|
1550
|
+
const msg = err instanceof Error ? err.message : String(err ?? "");
|
|
1551
|
+
return /is archived[.,]|Restore it before starting new work/i.test(msg);
|
|
1552
|
+
}
|
|
1553
|
+
/**
|
|
1554
|
+
* Restore an archived session (e.g. auto-archived by the Gateway's
|
|
1555
|
+
* active-session-cap) so new work can start. sessions.patch requires
|
|
1556
|
+
* expectedSessionId, so describe first, then patch archived=false.
|
|
1557
|
+
*/
|
|
1558
|
+
async unarchiveSession(key) {
|
|
1559
|
+
const canonical = this.canonicalSessionKey(key);
|
|
1560
|
+
try {
|
|
1561
|
+
const info = await this.rpc("sessions.describe", { key: canonical }, 15000);
|
|
1562
|
+
const sessionId = info?.session?.sessionId;
|
|
1563
|
+
if (!sessionId)
|
|
1564
|
+
return false;
|
|
1565
|
+
if (info?.session?.archived !== true)
|
|
1566
|
+
return true; // already active
|
|
1567
|
+
const res = await this.rpc("sessions.patch", { key: canonical, expectedSessionId: sessionId, archived: false }, 15000);
|
|
1568
|
+
console.log(`[OpenClaw] unarchived session ${key}: ${res?.ok === true ? "ok" : "unexpected response"}`);
|
|
1569
|
+
return res?.ok === true;
|
|
1570
|
+
}
|
|
1571
|
+
catch (err) {
|
|
1572
|
+
console.warn(`[OpenClaw] unarchive failed for ${key}:`, err instanceof Error ? err.message : err);
|
|
1573
|
+
return false;
|
|
1574
|
+
}
|
|
1575
|
+
}
|
|
1548
1576
|
async getSessionStatus(key) {
|
|
1549
1577
|
return this.rpc("sessions.describe", { key: this.canonicalSessionKey(key) });
|
|
1550
1578
|
}
|
|
@@ -1801,8 +1829,20 @@ export class OpenClawClient {
|
|
|
1801
1829
|
const maxLines = Number.isFinite(options.maxLines)
|
|
1802
1830
|
? Math.max(1, Math.floor(options.maxLines))
|
|
1803
1831
|
: undefined;
|
|
1832
|
+
const sendCompact = () => this.rpc("sessions.compact", { key: this.canonicalSessionKey(key), ...(maxLines !== undefined ? { maxLines } : {}) }, 10 * 60 * 1000);
|
|
1804
1833
|
try {
|
|
1805
|
-
|
|
1834
|
+
try {
|
|
1835
|
+
return await sendCompact();
|
|
1836
|
+
}
|
|
1837
|
+
catch (err) {
|
|
1838
|
+
if (!this.isArchivedSessionError(err))
|
|
1839
|
+
throw err;
|
|
1840
|
+
console.warn(`[OpenClaw] sessions.compact hit archived session for ${key}; restoring and retrying once`);
|
|
1841
|
+
const restored = await this.unarchiveSession(key);
|
|
1842
|
+
if (!restored)
|
|
1843
|
+
throw err;
|
|
1844
|
+
return await sendCompact();
|
|
1845
|
+
}
|
|
1806
1846
|
}
|
|
1807
1847
|
finally {
|
|
1808
1848
|
const mode = maxLines === undefined ? "semantic" : `maxLines=${maxLines}`;
|
|
@@ -2052,13 +2092,28 @@ export class OpenClawClient {
|
|
|
2052
2092
|
const sendStartedAt = Date.now();
|
|
2053
2093
|
try {
|
|
2054
2094
|
await params.onSendAttempt?.();
|
|
2055
|
-
|
|
2095
|
+
const sendChat = () => this.rpc("chat.send", {
|
|
2056
2096
|
sessionKey: this.canonicalSessionKey(sk),
|
|
2057
2097
|
message: params.message,
|
|
2058
2098
|
attachments: params.attachments,
|
|
2059
2099
|
deliver: params.deliver ?? false,
|
|
2060
2100
|
idempotencyKey: randomUUID(),
|
|
2061
2101
|
});
|
|
2102
|
+
try {
|
|
2103
|
+
result = await sendChat();
|
|
2104
|
+
}
|
|
2105
|
+
catch (err) {
|
|
2106
|
+
// The Gateway auto-archives sessions past the active-session cap; an
|
|
2107
|
+
// archived session refuses new work. Restore it once and retry — the
|
|
2108
|
+
// user should never have to fix this by hand.
|
|
2109
|
+
if (!this.isArchivedSessionError(err))
|
|
2110
|
+
throw err;
|
|
2111
|
+
console.warn(`[OpenClaw] chat.send hit archived session for ${sk}; restoring and retrying once`);
|
|
2112
|
+
const restored = await this.unarchiveSession(sk);
|
|
2113
|
+
if (!restored)
|
|
2114
|
+
throw err;
|
|
2115
|
+
result = await sendChat();
|
|
2116
|
+
}
|
|
2062
2117
|
}
|
|
2063
2118
|
finally {
|
|
2064
2119
|
releaseChatSendSlot();
|