openclaw-lark-multi-agent 1.1.2 → 1.2.1
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 +67 -2
- package/dist/compact-progress.d.ts +75 -0
- package/dist/compact-progress.js +177 -0
- package/dist/feishu-bot.d.ts +66 -0
- package/dist/feishu-bot.js +487 -14
- package/dist/live-status.js +13 -5
- package/dist/openclaw-client.js +40 -0
- package/dist/session-file-compactor.d.ts +39 -0
- package/dist/session-file-compactor.js +176 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -37,6 +37,7 @@ All of them connect to the same OpenClaw Gateway while keeping sessions, queues,
|
|
|
37
37
|
- Feishu image download and OpenClaw multimodal attachment forwarding
|
|
38
38
|
- Bridge attachment marker protocol for generated files/images/documents
|
|
39
39
|
- Feishu CardKit v2 Markdown rendering, including native table elements for pipe tables
|
|
40
|
+
- Live status card: in non-verbose mode each run shows a single self-updating Feishu interactive card (title + recent activity window with per-line timestamps + elapsed/model footer, refreshed once per second), which collapses to a compact one-line summary on a clean finish and retains the recent activity on failure for debugging
|
|
40
41
|
- Bridge-level slash commands and escaped OpenClaw slash commands
|
|
41
42
|
- `/discuss` mode for barrier-style multi-bot group discussion, including per-round markers and no-reply status notices
|
|
42
43
|
- `/chairman` role: a single per-group chairman that answers plain messages when no bot is in Free mode, and acts as host, challenger, and summarizer inside `/discuss`
|
|
@@ -231,10 +232,13 @@ Bridge-level commands use a single slash and are handled by this project:
|
|
|
231
232
|
- `/status` — show bot model, token usage, and session state
|
|
232
233
|
- `/compact` — compact the OpenClaw session
|
|
233
234
|
- `/reset` — reset the OpenClaw session
|
|
235
|
+
- `/stop` — force-stop a stuck run for this bot in this chat and unlock the queue
|
|
234
236
|
- `/verbose` — toggle tool-call messages for this bot in this chat
|
|
237
|
+
- `/livestatus [on|off]` — toggle, or explicitly enable/disable, the self-updating run-status card shown in non-verbose mode (default on)
|
|
235
238
|
- `/free [on|off]` — toggle, or explicitly enable/disable, this bot's Free mode in the current group chat
|
|
236
239
|
- `/mute` — toggle this bot's mute mode in the current group chat
|
|
237
240
|
- `/mode` — show this bot's current mode in the current chat
|
|
241
|
+
- `/model [id]` — show or switch this bot's bound model (persisted)
|
|
238
242
|
- `/discuss on|off|status|stop|rounds N` — control group-level multi-bot discussion mode (requires a chairman to enable; default 10 rounds)
|
|
239
243
|
- `/chairman [@Bot|off]` — set, view, or clear the single chairman for this group
|
|
240
244
|
- `/locale [zh|en]` — set or view this group's language
|
|
@@ -323,7 +327,7 @@ Persistent constraints (such as "do not call Feishu send tools directly" and the
|
|
|
323
327
|
`/discuss` is an explicit group-level multi-agent discussion scheduler. It is separate from Free mode:
|
|
324
328
|
|
|
325
329
|
- `/free` controls whether a single bot may answer plain human messages.
|
|
326
|
-
- `/discuss on` requires a chairman to be set first (`/chairman @Bot`). It lets one coordinator take over plain human messages and run all
|
|
330
|
+
- `/discuss on` requires a chairman to be set first (`/chairman @Bot`). It lets one coordinator take over plain human messages and run all non-muted bots plus the chairman in barrier-style rounds. Free mode is ignored inside discussion: every bot that is not muted participates regardless of its Free setting.
|
|
327
331
|
- Targeted mentions still fall through to normal routing, so `@GPT hello` works even while discuss mode is enabled.
|
|
328
332
|
- Each participant receives the same round prompt and does not see other participants' replies from the current round until the next round.
|
|
329
333
|
- The chairman speaks last each round: it gives its own view, challenges weak points, mediates disagreements, and decides whether to continue or conclude.
|
|
@@ -396,6 +400,23 @@ The bridge subscribes to Feishu `im.message.recalled_v1` events. When a user rec
|
|
|
396
400
|
|
|
397
401
|
Version 1 behavior intentionally does **not** abort an OpenClaw run that has already started processing the recalled message, and it does not recall bot replies that were already sent. The first goal is to make recall reliable for queued/not-yet-processed messages.
|
|
398
402
|
|
|
403
|
+
## Session health and error recovery
|
|
404
|
+
|
|
405
|
+
The bridge distinguishes a failed *run* from a dead *session*:
|
|
406
|
+
|
|
407
|
+
- During an active run, a health monitor polls the OpenClaw session. Only true
|
|
408
|
+
session-death states (`killed` / `dead` / `crashed`, matched on word
|
|
409
|
+
boundaries) are treated as unhealthy; run-level outcomes such as `aborted`,
|
|
410
|
+
`error`, or `timeout` are not, because the session usually remains usable for
|
|
411
|
+
the next message. A short re-check guards against transient blips.
|
|
412
|
+
- When a session is genuinely unhealthy, the bridge warns the user but does not
|
|
413
|
+
force a `/reset`: the current message is still attempted, and only an in-run
|
|
414
|
+
confirmation of death stops the wait. Users decide whether to retry.
|
|
415
|
+
- Recoverable agent errors are not surfaced as failures. When OpenClaw emits a
|
|
416
|
+
recoverable lifecycle error (for example `Context overflow: prompt too large`)
|
|
417
|
+
it auto-compacts and keeps the same run alive; the bridge defers instead of
|
|
418
|
+
rejecting, so a later real final reply wins. Only a genuine stall falls back to
|
|
419
|
+
the deferred error via the idle timeout. Non-recoverable errors still fail fast.
|
|
399
420
|
|
|
400
421
|
## Markdown, tables, and attachments
|
|
401
422
|
|
|
@@ -408,6 +429,50 @@ Assistant replies are sent as Feishu CardKit v2 cards. Markdown is preprocessed
|
|
|
408
429
|
|
|
409
430
|
For generated files/images/documents, agents should use the bridge attachment marker protocol instead of calling Feishu messaging tools directly. The bridge strips the marker from the visible reply, validates the file path under the configured attachment directory, uploads/sends the attachment, and records it in local context. Markdown documents can be converted into Feishu cloud documents through this path.
|
|
410
431
|
|
|
432
|
+
## Live status
|
|
433
|
+
|
|
434
|
+
In non-verbose mode, each run shows a single self-updating Feishu interactive
|
|
435
|
+
card so users can see progress without the noise of per-tool-call messages.
|
|
436
|
+
Verbose mode and live status are mutually exclusive: when `/verbose` is on, the
|
|
437
|
+
bridge emits the existing per-tool messages instead of a live status card.
|
|
438
|
+
|
|
439
|
+
While running, the card shows:
|
|
440
|
+
|
|
441
|
+
- a title (`<bot> is working`);
|
|
442
|
+
- a rolling window of the most recent activity lines (tool start `▸`, tool end
|
|
443
|
+
`✓`, and intermediate assistant text `•`), each prefixed with the relative
|
|
444
|
+
time `mm:ss` since the run started;
|
|
445
|
+
- a footer with elapsed time and the bound model name.
|
|
446
|
+
|
|
447
|
+
The card is created lazily (a fast reply that finishes within the create delay
|
|
448
|
+
never spawns a card, to avoid flicker) and is updated with `im.message.patch`,
|
|
449
|
+
which has no 20-edit cap. A 1-second ticker advances the elapsed timer even when
|
|
450
|
+
no new activity arrives.
|
|
451
|
+
|
|
452
|
+
When the run ends:
|
|
453
|
+
|
|
454
|
+
- a clean finish (normal reply or `NO_REPLY`) collapses the card to a single
|
|
455
|
+
compact grey line: status emoji + total tool calls + total elapsed;
|
|
456
|
+
- a failure (provider error, killed/unhealthy session, delivery error, or idle
|
|
457
|
+
timeout) keeps the recent activity window plus the summary, with an orange
|
|
458
|
+
header, so the steps leading up to the failure stay visible for debugging.
|
|
459
|
+
|
|
460
|
+
The final answer is always delivered separately through the normal interactive-card
|
|
461
|
+
delivery path, so Markdown renders correctly; the live status card is a distinct
|
|
462
|
+
message and never replaces or blocks the final reply.
|
|
463
|
+
|
|
464
|
+
Live status is on by default and can be toggled per bot/chat with
|
|
465
|
+
`/livestatus [on|off]`. It can be disabled globally by setting
|
|
466
|
+
`OPENCLAW_LARK_MULTI_AGENT_LIVE_STATUS=0`. Tunable defaults:
|
|
467
|
+
|
|
468
|
+
| Environment variable | Default | Meaning |
|
|
469
|
+
| --- | --- | --- |
|
|
470
|
+
| `OPENCLAW_LARK_MULTI_AGENT_LIVE_STATUS` | `1` | Master switch; `0` disables live status for all bots |
|
|
471
|
+
| `OPENCLAW_LARK_MULTI_AGENT_LIVE_STATUS_DELAY_MS` | `800` | Delay before creating the card, so fast replies do not flicker one |
|
|
472
|
+
| `OPENCLAW_LARK_MULTI_AGENT_LIVE_STATUS_TICK_MS` | `1000` | How often the elapsed-time footer auto-refreshes |
|
|
473
|
+
| `OPENCLAW_LARK_MULTI_AGENT_LIVE_STATUS_HISTORY` | `6` | Number of recent activity lines kept in the card |
|
|
474
|
+
| `OPENCLAW_LARK_MULTI_AGENT_LIVE_STATUS_MAX_CHARS` | `120` | Per-line character cap before truncation |
|
|
475
|
+
|
|
411
476
|
## Data model
|
|
412
477
|
|
|
413
478
|
SQLite state lives in the configured data directory. Important tables:
|
|
@@ -421,7 +486,7 @@ SQLite state lives in the configured data directory. Important tables:
|
|
|
421
486
|
- `delivery_outbox` — durable user-visible delivery ledger with claim/dedupe state
|
|
422
487
|
- `recalled_messages` — recalled user messages excluded from pending work and future context
|
|
423
488
|
- `processed_events` — Feishu event de-duplication
|
|
424
|
-
- `bot_chat_settings` — per-bot/per-chat settings such as verbose mode
|
|
489
|
+
- `bot_chat_settings` — per-bot/per-chat settings such as verbose mode, mode, and live-status toggle
|
|
425
490
|
|
|
426
491
|
## Development
|
|
427
492
|
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/** Coarse phase of the compaction flow, drives the card's primary line. */
|
|
2
|
+
export type CompactPhase = "native" | "tool-trim";
|
|
3
|
+
/** Terminal outcome used by the renderer to pick color/emoji. */
|
|
4
|
+
export type CompactState = "running" | "done" | "failed" | "noop";
|
|
5
|
+
/** Structured snapshot the renderer turns into a Feishu interactive card. */
|
|
6
|
+
export type CompactProgressView = {
|
|
7
|
+
state: CompactState;
|
|
8
|
+
/** Current phase while running (ignored once terminal). */
|
|
9
|
+
phase: CompactPhase;
|
|
10
|
+
/** Elapsed time like "0:42". */
|
|
11
|
+
elapsed: string;
|
|
12
|
+
/** Terminal detail line, e.g. "12.3M→4.1M (-67%)" or an error/skip reason. */
|
|
13
|
+
detail?: string;
|
|
14
|
+
};
|
|
15
|
+
export type CompactProgressCallbacks = {
|
|
16
|
+
/** Create the status card; returns the new message id (undefined disables). */
|
|
17
|
+
create: (view: CompactProgressView) => Promise<string | undefined>;
|
|
18
|
+
/** Patch the existing status card. */
|
|
19
|
+
edit: (messageId: string, view: CompactProgressView) => Promise<void>;
|
|
20
|
+
warn?: (message: string, err?: unknown) => void;
|
|
21
|
+
};
|
|
22
|
+
export type CompactProgressOptions = {
|
|
23
|
+
locale?: "zh" | "en";
|
|
24
|
+
/** Delay before the card first appears, to avoid flashing on fast compactions. */
|
|
25
|
+
delayMs?: number;
|
|
26
|
+
/** Footer/auto-refresh cadence in ms. */
|
|
27
|
+
tickMs?: number;
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Lifecycle: start() → (optional) toToolTrim() when native gives up → one of
|
|
31
|
+
* done()/noop()/fail(). The card is created lazily after `delayMs` so a fast
|
|
32
|
+
* compaction that finishes first never shows a card at all. All terminal calls
|
|
33
|
+
* are idempotent and safe even if the card was never created.
|
|
34
|
+
*/
|
|
35
|
+
export declare class CompactProgressController {
|
|
36
|
+
private readonly callbacks;
|
|
37
|
+
private readonly opts;
|
|
38
|
+
private messageId?;
|
|
39
|
+
private startedAt;
|
|
40
|
+
private phase;
|
|
41
|
+
private state;
|
|
42
|
+
private detail?;
|
|
43
|
+
private createTimer?;
|
|
44
|
+
private tickTimer?;
|
|
45
|
+
private createPromise?;
|
|
46
|
+
private finalized;
|
|
47
|
+
private disabled;
|
|
48
|
+
private lastSignature;
|
|
49
|
+
constructor(callbacks: CompactProgressCallbacks, opts?: CompactProgressOptions);
|
|
50
|
+
get id(): string | undefined;
|
|
51
|
+
/** Begin the flow; schedules lazy card creation after delayMs. */
|
|
52
|
+
start(): void;
|
|
53
|
+
/** Native compaction gave up (timeout/no-op); switch the card to fast-trim. */
|
|
54
|
+
toToolTrim(): Promise<void>;
|
|
55
|
+
/** Finished successfully; patch the card to the done state with a detail line. */
|
|
56
|
+
done(detail?: string): Promise<void>;
|
|
57
|
+
/** Nothing to compact; patch the card to a neutral "no-op" state. */
|
|
58
|
+
noop(detail?: string): Promise<void>;
|
|
59
|
+
/** Failed; patch the card to the failed state with an error detail line. */
|
|
60
|
+
fail(detail?: string): Promise<void>;
|
|
61
|
+
/** Tear down timers without sending anything (e.g. on unexpected disposal). */
|
|
62
|
+
dispose(): void;
|
|
63
|
+
private finish;
|
|
64
|
+
private refresh;
|
|
65
|
+
private ensureCreatedNow;
|
|
66
|
+
private ensureCreated;
|
|
67
|
+
/** Refresh the elapsed-time footer periodically even with no phase change. */
|
|
68
|
+
private startTicker;
|
|
69
|
+
private safeEdit;
|
|
70
|
+
private stopTimers;
|
|
71
|
+
/** Dedupe key; includes elapsed so the ticking footer still advances. */
|
|
72
|
+
private signature;
|
|
73
|
+
private formatElapsed;
|
|
74
|
+
buildView(): CompactProgressView;
|
|
75
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
// A tiny live-status controller dedicated to the /compact (and auto-compact)
|
|
2
|
+
// flow, so the user gets a visible "正在压缩…" card that ticks while a large
|
|
3
|
+
// session is being compacted — instead of staring at nothing for tens of
|
|
4
|
+
// seconds while native compaction grinds (and sometimes times out).
|
|
5
|
+
//
|
|
6
|
+
// It deliberately does NOT reuse LiveStatusController: that one models a tool
|
|
7
|
+
// run (activity window, tool-call count, model footer). Compaction has just a
|
|
8
|
+
// few coarse phases and a single elapsed timer, so a focused, self-contained
|
|
9
|
+
// controller keeps the card clean and the logic trivial to test.
|
|
10
|
+
const DEFAULT_DELAY_MS = Number(process.env.OPENCLAW_LARK_MULTI_AGENT_COMPACT_STATUS_DELAY_MS || 500);
|
|
11
|
+
const DEFAULT_TICK_MS = Number(process.env.OPENCLAW_LARK_MULTI_AGENT_COMPACT_STATUS_TICK_MS || 1000);
|
|
12
|
+
/**
|
|
13
|
+
* Lifecycle: start() → (optional) toToolTrim() when native gives up → one of
|
|
14
|
+
* done()/noop()/fail(). The card is created lazily after `delayMs` so a fast
|
|
15
|
+
* compaction that finishes first never shows a card at all. All terminal calls
|
|
16
|
+
* are idempotent and safe even if the card was never created.
|
|
17
|
+
*/
|
|
18
|
+
export class CompactProgressController {
|
|
19
|
+
callbacks;
|
|
20
|
+
opts;
|
|
21
|
+
messageId;
|
|
22
|
+
startedAt = Date.now();
|
|
23
|
+
phase = "native";
|
|
24
|
+
state = "running";
|
|
25
|
+
detail;
|
|
26
|
+
createTimer;
|
|
27
|
+
tickTimer;
|
|
28
|
+
createPromise;
|
|
29
|
+
finalized = false;
|
|
30
|
+
disabled = false;
|
|
31
|
+
lastSignature = "";
|
|
32
|
+
constructor(callbacks, opts = {}) {
|
|
33
|
+
this.callbacks = callbacks;
|
|
34
|
+
this.opts = opts;
|
|
35
|
+
}
|
|
36
|
+
get id() { return this.messageId; }
|
|
37
|
+
/** Begin the flow; schedules lazy card creation after delayMs. */
|
|
38
|
+
start() {
|
|
39
|
+
if (this.disabled || this.finalized || this.createTimer || this.messageId)
|
|
40
|
+
return;
|
|
41
|
+
this.startedAt = Date.now();
|
|
42
|
+
this.createTimer = setTimeout(() => {
|
|
43
|
+
this.createTimer = undefined;
|
|
44
|
+
this.createPromise = this.ensureCreated();
|
|
45
|
+
}, this.opts.delayMs ?? DEFAULT_DELAY_MS);
|
|
46
|
+
this.createTimer.unref?.();
|
|
47
|
+
}
|
|
48
|
+
/** Native compaction gave up (timeout/no-op); switch the card to fast-trim. */
|
|
49
|
+
async toToolTrim() {
|
|
50
|
+
if (this.finalized || this.phase === "tool-trim")
|
|
51
|
+
return;
|
|
52
|
+
this.phase = "tool-trim";
|
|
53
|
+
await this.refresh();
|
|
54
|
+
}
|
|
55
|
+
/** Finished successfully; patch the card to the done state with a detail line. */
|
|
56
|
+
async done(detail) {
|
|
57
|
+
await this.finish("done", detail);
|
|
58
|
+
}
|
|
59
|
+
/** Nothing to compact; patch the card to a neutral "no-op" state. */
|
|
60
|
+
async noop(detail) {
|
|
61
|
+
await this.finish("noop", detail);
|
|
62
|
+
}
|
|
63
|
+
/** Failed; patch the card to the failed state with an error detail line. */
|
|
64
|
+
async fail(detail) {
|
|
65
|
+
await this.finish("failed", detail);
|
|
66
|
+
}
|
|
67
|
+
/** Tear down timers without sending anything (e.g. on unexpected disposal). */
|
|
68
|
+
dispose() {
|
|
69
|
+
this.finalized = true;
|
|
70
|
+
this.stopTimers();
|
|
71
|
+
}
|
|
72
|
+
async finish(state, detail) {
|
|
73
|
+
if (this.finalized)
|
|
74
|
+
return;
|
|
75
|
+
this.state = state;
|
|
76
|
+
this.detail = detail;
|
|
77
|
+
this.finalized = true;
|
|
78
|
+
this.stopTimers();
|
|
79
|
+
// Wait for any in-flight lazy create so we can patch (not orphan) the card.
|
|
80
|
+
if (this.createPromise)
|
|
81
|
+
await this.createPromise.catch(() => { });
|
|
82
|
+
if (!this.messageId || this.disabled)
|
|
83
|
+
return;
|
|
84
|
+
await this.safeEdit(true);
|
|
85
|
+
}
|
|
86
|
+
async refresh() {
|
|
87
|
+
if (this.finalized || this.disabled)
|
|
88
|
+
return;
|
|
89
|
+
await this.ensureCreatedNow();
|
|
90
|
+
await this.safeEdit();
|
|
91
|
+
}
|
|
92
|
+
async ensureCreatedNow() {
|
|
93
|
+
if (this.createTimer) {
|
|
94
|
+
clearTimeout(this.createTimer);
|
|
95
|
+
this.createTimer = undefined;
|
|
96
|
+
this.createPromise = this.ensureCreated();
|
|
97
|
+
}
|
|
98
|
+
if (this.createPromise)
|
|
99
|
+
await this.createPromise.catch(() => { });
|
|
100
|
+
}
|
|
101
|
+
async ensureCreated() {
|
|
102
|
+
if (this.disabled || this.finalized || this.messageId)
|
|
103
|
+
return;
|
|
104
|
+
try {
|
|
105
|
+
const id = await this.callbacks.create(this.buildView());
|
|
106
|
+
if (!id) {
|
|
107
|
+
this.disabled = true;
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
this.messageId = id;
|
|
111
|
+
this.lastSignature = this.signature();
|
|
112
|
+
this.startTicker();
|
|
113
|
+
}
|
|
114
|
+
catch (err) {
|
|
115
|
+
this.disabled = true;
|
|
116
|
+
this.callbacks.warn?.("compact progress create failed", err);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
/** Refresh the elapsed-time footer periodically even with no phase change. */
|
|
120
|
+
startTicker() {
|
|
121
|
+
if (this.tickTimer || this.disabled || this.finalized)
|
|
122
|
+
return;
|
|
123
|
+
const tickMs = this.opts.tickMs ?? DEFAULT_TICK_MS;
|
|
124
|
+
this.tickTimer = setInterval(() => {
|
|
125
|
+
if (this.finalized || this.disabled || !this.messageId)
|
|
126
|
+
return;
|
|
127
|
+
void this.safeEdit();
|
|
128
|
+
}, tickMs);
|
|
129
|
+
this.tickTimer.unref?.();
|
|
130
|
+
}
|
|
131
|
+
async safeEdit(force = false) {
|
|
132
|
+
if (!this.messageId || this.disabled)
|
|
133
|
+
return;
|
|
134
|
+
if (!force && this.finalized)
|
|
135
|
+
return;
|
|
136
|
+
const sig = this.signature();
|
|
137
|
+
if (!force && sig === this.lastSignature)
|
|
138
|
+
return;
|
|
139
|
+
try {
|
|
140
|
+
await this.callbacks.edit(this.messageId, this.buildView());
|
|
141
|
+
this.lastSignature = sig;
|
|
142
|
+
}
|
|
143
|
+
catch (err) {
|
|
144
|
+
this.disabled = true;
|
|
145
|
+
this.stopTimers();
|
|
146
|
+
this.callbacks.warn?.("compact progress edit failed", err);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
stopTimers() {
|
|
150
|
+
if (this.createTimer) {
|
|
151
|
+
clearTimeout(this.createTimer);
|
|
152
|
+
this.createTimer = undefined;
|
|
153
|
+
}
|
|
154
|
+
if (this.tickTimer) {
|
|
155
|
+
clearInterval(this.tickTimer);
|
|
156
|
+
this.tickTimer = undefined;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
/** Dedupe key; includes elapsed so the ticking footer still advances. */
|
|
160
|
+
signature() {
|
|
161
|
+
return `${this.state}|${this.phase}|${this.formatElapsed()}|${this.detail || ""}`;
|
|
162
|
+
}
|
|
163
|
+
formatElapsed() {
|
|
164
|
+
const totalSec = Math.max(0, Math.floor((Date.now() - this.startedAt) / 1000));
|
|
165
|
+
const m = Math.floor(totalSec / 60);
|
|
166
|
+
const s = totalSec % 60;
|
|
167
|
+
return `${m}:${String(s).padStart(2, "0")}`;
|
|
168
|
+
}
|
|
169
|
+
buildView() {
|
|
170
|
+
return {
|
|
171
|
+
state: this.state,
|
|
172
|
+
phase: this.phase,
|
|
173
|
+
elapsed: this.formatElapsed(),
|
|
174
|
+
detail: this.detail,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
}
|
package/dist/feishu-bot.d.ts
CHANGED
|
@@ -34,6 +34,8 @@ export declare class FeishuBot {
|
|
|
34
34
|
private delayedFailureTimers;
|
|
35
35
|
/** Last time a real assistant-visible reply was successfully handed to the delivery pipeline. */
|
|
36
36
|
private lastRealDeliveryAt;
|
|
37
|
+
/** Per-chat: whether we've already sent the high-context alert this cycle. */
|
|
38
|
+
private contextAlerted;
|
|
37
39
|
/** Active chatSend trigger target so final replies and proactive session.message share one delivery key. */
|
|
38
40
|
private activeDeliveryTargets;
|
|
39
41
|
private adminOpenId;
|
|
@@ -117,6 +119,51 @@ export declare class FeishuBot {
|
|
|
117
119
|
private isBridgeControlReply;
|
|
118
120
|
private isLegacyBridgeControlMessage;
|
|
119
121
|
private isRuntimeFailureText;
|
|
122
|
+
/**
|
|
123
|
+
* Is this main-run error worth auto-retrying (by resuming the session via the
|
|
124
|
+
* probe loop)? Mainly transient failures — request/response timeouts and
|
|
125
|
+
* aborted/incomplete runs. We do NOT auto-retry clearly terminal errors
|
|
126
|
+
* (auth/permission/quota/billing/model-not-supported), which would just fail
|
|
127
|
+
* again. The error text comes from collectReply's `Agent error: <msg>`.
|
|
128
|
+
*/
|
|
129
|
+
private isRetryableAgentError;
|
|
130
|
+
/**
|
|
131
|
+
* Heuristic: does this reply look truncated / mid-task? Bias toward false
|
|
132
|
+
* positives (“宁可错杀不放过”) — a false positive only costs one invisible
|
|
133
|
+
* confirmation round, and the session self-check is the real arbiter. We gate
|
|
134
|
+
* only on cheap text shape here, never on stopReason (the upstream rarely
|
|
135
|
+
* provides one for normal-looking-but-truncated finals).
|
|
136
|
+
*/
|
|
137
|
+
private looksTruncated;
|
|
138
|
+
/** Current context usage percent for a session (totalTokens / contextTokens),
|
|
139
|
+
* or 0 if it cannot be determined. Same basis as /status. */
|
|
140
|
+
private getContextUsagePercent;
|
|
141
|
+
/**
|
|
142
|
+
* After a reply is delivered, alert the user once if context usage crossed the
|
|
143
|
+
* threshold (default 80%), so they can compact before a session bloats into the
|
|
144
|
+
* timeout/lock/overflow failure modes. We only alert on a fresh crossing: once
|
|
145
|
+
* alerted we stay quiet until usage drops back below the threshold (e.g. after
|
|
146
|
+
* a compact), then re-arm.
|
|
147
|
+
*/
|
|
148
|
+
private maybeAlertHighContext;
|
|
149
|
+
/** True when the agent's reply is exactly a completion phrase (zh or en,
|
|
150
|
+
* tolerant of trailing punctuation/whitespace and case). */
|
|
151
|
+
/**
|
|
152
|
+
* True when the agent's reply means “done”. We accept the exact phrase as well
|
|
153
|
+
* as common wrappers (“已经结束了” / “好的,结束了” / “yes, DONE”), but reject it
|
|
154
|
+
* when a negation/not-finished cue is present (“还没结束” / “not done”), so a
|
|
155
|
+
* “still working” answer never counts as completion.
|
|
156
|
+
*/
|
|
157
|
+
private isDonePhrase;
|
|
158
|
+
/**
|
|
159
|
+
* Auto-retry loop: if `reply` looks truncated, ask the same session whether it
|
|
160
|
+
* finished. The session is the arbiter: if it replies exactly the done phrase,
|
|
161
|
+
* the ORIGINAL reply was the real result and we deliver that. Otherwise the
|
|
162
|
+
* session kept working and its new output becomes the latest reply, which we
|
|
163
|
+
* re-check. Loops until the session confirms completion, a reply no longer
|
|
164
|
+
* looks truncated, the budget is spent, or the session cannot answer.
|
|
165
|
+
*/
|
|
166
|
+
private autoRetryUntilComplete;
|
|
120
167
|
private cancelDelayedFailure;
|
|
121
168
|
private setActiveDeliveryTarget;
|
|
122
169
|
private scheduleDelayedFailure;
|
|
@@ -153,6 +200,14 @@ export declare class FeishuBot {
|
|
|
153
200
|
private sendLiveStatusCard;
|
|
154
201
|
private replyLiveStatusCard;
|
|
155
202
|
private patchLiveStatusCard;
|
|
203
|
+
/**
|
|
204
|
+
* Render the /compact progress card. While running it shows a phase line plus a
|
|
205
|
+
* ticking elapsed footer so the user can see compaction is in progress; when
|
|
206
|
+
* terminal it collapses to a single compact line (success/skip/fail).
|
|
207
|
+
*/
|
|
208
|
+
private buildCompactProgressCard;
|
|
209
|
+
private replyCompactCard;
|
|
210
|
+
private patchCompactCard;
|
|
156
211
|
private sendTextMessage;
|
|
157
212
|
private replyTextMessage;
|
|
158
213
|
private editTextMessage;
|
|
@@ -202,6 +257,17 @@ export declare class FeishuBot {
|
|
|
202
257
|
/**
|
|
203
258
|
* Handle /compact command: compress session context.
|
|
204
259
|
*/
|
|
260
|
+
/**
|
|
261
|
+
* Compact a session with a fallback chain:
|
|
262
|
+
* 1. OpenClaw's native (LLM-summary) compaction — best semantics.
|
|
263
|
+
* 2. If that did not compact (or threw), fall back to "tool-trim": rewrite
|
|
264
|
+
* the transcript file to drop tool calls/results while keeping the
|
|
265
|
+
* conversation. This needs no model, so it works no matter how large the
|
|
266
|
+
* session is (the native path itself fails to fit an oversized session
|
|
267
|
+
* into the model). A backup is always written before any file change.
|
|
268
|
+
* Returns a structured result describing what happened.
|
|
269
|
+
*/
|
|
270
|
+
private compactWithFallback;
|
|
205
271
|
private handleCompactCommand;
|
|
206
272
|
/**
|
|
207
273
|
* Handle /reset command: fire sessions.reset and confirm.
|