openclaw-lark-multi-agent 1.4.10 → 1.4.11
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 +13 -3
- package/README.zh-CN.md +10 -2
- package/dist/feishu-bot.js +2 -2
- package/dist/live-status.d.ts +0 -3
- package/dist/live-status.js +3 -12
- package/dist/openclaw-client.d.ts +2 -0
- package/dist/openclaw-client.js +92 -43
- package/dist/session-status.d.ts +2 -1
- package/dist/session-status.js +3 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -601,10 +601,20 @@ State and context usage come from one bounded Gateway status lookup. K values
|
|
|
601
601
|
are rounded integers (1000 tokens per K); unavailable/stale values are not shown
|
|
602
602
|
as zero. The footer is a send-time snapshot, not a live status widget.
|
|
603
603
|
|
|
604
|
-
LMA
|
|
605
|
-
|
|
606
|
-
|
|
604
|
+
LMA pauses foreground waiting after **10 consecutive minutes without effective
|
|
605
|
+
task activity**, not after ten minutes of total runtime. New owned assistant
|
|
606
|
+
text or distinct tool start/completion resets the collector's idle clock;
|
|
607
|
+
card elapsed ticks, status polling, usage-only and duplicate events do not.
|
|
608
|
+
There is no independent card lifetime, and time before chat.send admission /
|
|
609
|
+
collector start is not charged as task silence. A write completion at 9:35 means
|
|
610
|
+
no silence freeze before 19:35, not a freeze at 10:00.
|
|
611
|
+
LMA sends a normal wait notice rather than claiming execution failed, displaying
|
|
612
|
+
the actual status even if unavailable. Confirmed chat-final `yielded: true`
|
|
613
|
+
still pauses immediately. Once frozen, the old card does not auto-resume after
|
|
614
|
+
later tool activity (same published policy for silence and yield); background
|
|
607
615
|
result handling and the original queue remain, with no automatic abort/replay.
|
|
608
616
|
Real results are sent separately; necessary terminal card cleanup is retained.
|
|
609
617
|
This does not shorten the Gateway's execution limit. Unverified steer V2 and
|
|
610
618
|
question adapters are not included in this release.
|
|
619
|
+
|
|
620
|
+
Timing/API parameters and limitations: [foreground idle semantics](docs/foreground-idle-2026-09-11.md).
|
package/README.zh-CN.md
CHANGED
|
@@ -509,8 +509,16 @@ MIT
|
|
|
509
509
|
1000 token 四舍五入为整数。缺失或明确过期的数据不冒充 0。尾部反映发送前
|
|
510
510
|
查询的状态,不会持续自动刷新历史回复。
|
|
511
511
|
|
|
512
|
-
|
|
513
|
-
|
|
512
|
+
前台等待改为**连续 10 分钟没有有效任务活动才暂停**,不是任务总时长上限。
|
|
513
|
+
已归属本次任务的新 assistant 文本、不同工具调用的开始/结束等会重置 collector
|
|
514
|
+
的静默计时;卡片 elapsed ticker、状态轮询、usage-only 与重复事件不会续时。
|
|
515
|
+
卡片没有独立总时长计时器;chat.send 接受及 collector 启动前的排队/内部耗时不算
|
|
516
|
+
任务静默。9:35 的 write 完成后,最早 19:35 静默到期,不能在 10:00 冻结。
|
|
517
|
+
到期发送普通等待提示,显示实际状态(不可用也不冒充 running),不判任务失败。
|
|
518
|
+
真正的 chat-final `yielded: true` 仍立即提示并冻结,不等 10 分钟。
|
|
519
|
+
普通静默暂停与 yield 都沿用已发布策略:后续工具活动不会自动解冻旧卡。
|
|
514
520
|
后台仍监听原任务,保留原队列,不因此停止或自动重发请求。真实结果另行回复,
|
|
515
521
|
保留必要的终态收尾。本改动不缩短 Gateway 的执行时限,也不包含未验收的
|
|
516
522
|
steer V2 与问答适配器。
|
|
523
|
+
|
|
524
|
+
计时、API 参数及限制详见 [前台静默计时说明](docs/foreground-idle-2026-09-11.md)。
|
package/dist/feishu-bot.js
CHANGED
|
@@ -1361,8 +1361,8 @@ export class FeishuBot {
|
|
|
1361
1361
|
currentMessage: mergedContent,
|
|
1362
1362
|
currentSenderName: lastHuman.senderName,
|
|
1363
1363
|
deliver: false,
|
|
1364
|
-
//
|
|
1365
|
-
//
|
|
1364
|
+
// Pause foreground only after consecutive effective-activity silence;
|
|
1365
|
+
// active long-running tool loops are not a failure.
|
|
1366
1366
|
timeoutMs: FOREGROUND_WAIT_MS,
|
|
1367
1367
|
includeContext: !isNativeCommandTrigger,
|
|
1368
1368
|
includeBridgeAttachmentHint: !isNativeCommandTrigger,
|
package/dist/live-status.d.ts
CHANGED
|
@@ -50,8 +50,6 @@ export type LiveStatusOptions = {
|
|
|
50
50
|
historySize?: number;
|
|
51
51
|
/** Footer/auto-refresh cadence in ms. */
|
|
52
52
|
tickMs?: number;
|
|
53
|
-
/** Fixed card refresh lifetime; activity does not extend it. */
|
|
54
|
-
refreshBudgetMs?: number;
|
|
55
53
|
};
|
|
56
54
|
export declare class LiveStatusController {
|
|
57
55
|
private readonly callbacks;
|
|
@@ -63,7 +61,6 @@ export declare class LiveStatusController {
|
|
|
63
61
|
private startedAt;
|
|
64
62
|
private createTimer?;
|
|
65
63
|
private tickTimer?;
|
|
66
|
-
private refreshBudgetTimer?;
|
|
67
64
|
private createPromise?;
|
|
68
65
|
private finalized;
|
|
69
66
|
private waitingForResult;
|
package/dist/live-status.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { FOREGROUND_WAIT_MS } from "./session-status.js";
|
|
2
1
|
const DEFAULT_DELAY_MS = Number(process.env.OPENCLAW_LARK_MULTI_AGENT_LIVE_STATUS_DELAY_MS || 800);
|
|
3
2
|
const DEFAULT_MAX_CHARS = Number(process.env.OPENCLAW_LARK_MULTI_AGENT_LIVE_STATUS_MAX_CHARS || 120);
|
|
4
3
|
// How many recent activity lines to keep in the card content area.
|
|
@@ -23,7 +22,6 @@ export class LiveStatusController {
|
|
|
23
22
|
startedAt = Date.now();
|
|
24
23
|
createTimer;
|
|
25
24
|
tickTimer;
|
|
26
|
-
refreshBudgetTimer;
|
|
27
25
|
createPromise;
|
|
28
26
|
finalized = false;
|
|
29
27
|
waitingForResult = false;
|
|
@@ -49,12 +47,9 @@ export class LiveStatusController {
|
|
|
49
47
|
if (this.disabled || this.finalized || this.updatesFrozen || this.createTimer || this.messageId)
|
|
50
48
|
return;
|
|
51
49
|
this.startedAt = Date.now();
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
: "前台刷新时间已到,过程卡停止实时刷新;后台继续接收结果。").catch(err => this.callbacks.warn?.("live status freeze failed", err));
|
|
56
|
-
}, this.opts.refreshBudgetMs ?? FOREGROUND_WAIT_MS);
|
|
57
|
-
this.refreshBudgetTimer.unref?.();
|
|
50
|
+
// Presentation may start before chat.send admission. Only the collector's
|
|
51
|
+
// effective-activity idle decision (or confirmed yield) freezes this card;
|
|
52
|
+
// elapsed ticks are presentation, never evidence of task progress.
|
|
58
53
|
if (initialDetail && initialDetail.trim()) {
|
|
59
54
|
this.pushLine("lifecycle", initialDetail.trim());
|
|
60
55
|
}
|
|
@@ -253,10 +248,6 @@ export class LiveStatusController {
|
|
|
253
248
|
this.lines = this.lines.slice(this.lines.length - limit);
|
|
254
249
|
}
|
|
255
250
|
stopTimers() {
|
|
256
|
-
if (this.refreshBudgetTimer) {
|
|
257
|
-
clearTimeout(this.refreshBudgetTimer);
|
|
258
|
-
this.refreshBudgetTimer = undefined;
|
|
259
|
-
}
|
|
260
251
|
if (this.createTimer) {
|
|
261
252
|
clearTimeout(this.createTimer);
|
|
262
253
|
this.createTimer = undefined;
|
|
@@ -226,6 +226,7 @@ export declare class OpenClawClient {
|
|
|
226
226
|
message: string;
|
|
227
227
|
attachments?: ChatAttachment[];
|
|
228
228
|
deliver?: boolean;
|
|
229
|
+
/** Consecutive effective-activity silence after admission/collector start; default 10 minutes, not total run time. */
|
|
229
230
|
timeoutMs?: number;
|
|
230
231
|
emptyFinalAsNoReply?: boolean;
|
|
231
232
|
/** Called immediately before issuing chat.send RPC; use for at-most-once bookkeeping. */
|
|
@@ -256,6 +257,7 @@ export declare class OpenClawClient {
|
|
|
256
257
|
currentMessage: string;
|
|
257
258
|
currentSenderName: string;
|
|
258
259
|
deliver?: boolean;
|
|
260
|
+
/** Consecutive effective-activity silence after admission/collector start; default 10 minutes, not total run time. */
|
|
259
261
|
timeoutMs?: number;
|
|
260
262
|
emptyFinalAsNoReply?: boolean;
|
|
261
263
|
/** Native escaped commands (//status -> /status) should not receive catch-up context. */
|
package/dist/openclaw-client.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import WebSocket from "ws";
|
|
2
2
|
import { SessionWaitPaused, normalizeSessionRuntimeStatus, isWaitTimeout, FOREGROUND_WAIT_MS } from "./session-status.js";
|
|
3
|
-
import { randomUUID } from "crypto";
|
|
3
|
+
import { createHash, randomUUID } from "crypto";
|
|
4
4
|
import { mkdirSync, readFileSync, statSync, writeFileSync } from "fs";
|
|
5
5
|
import { basename, extname, join, resolve, sep } from "path";
|
|
6
6
|
import { getBridgeAttachmentsDir, getDataDir } from "./paths.js";
|
|
@@ -759,7 +759,6 @@ export class OpenClawClient {
|
|
|
759
759
|
let foregroundPaused = false;
|
|
760
760
|
let foregroundYielded = false;
|
|
761
761
|
let turnYielded = false;
|
|
762
|
-
let foregroundWaitTimer;
|
|
763
762
|
const handleTimeoutResult = async (error) => {
|
|
764
763
|
if (observationClosed)
|
|
765
764
|
return;
|
|
@@ -771,8 +770,6 @@ export class OpenClawClient {
|
|
|
771
770
|
// Foreground notice, background observation. Keep the original queue
|
|
772
771
|
// owner/progress path so later input still steers into the same run.
|
|
773
772
|
foregroundPaused = true;
|
|
774
|
-
if (foregroundWaitTimer)
|
|
775
|
-
clearTimeout(foregroundWaitTimer);
|
|
776
773
|
if (!waitNoticeSent) {
|
|
777
774
|
waitNoticeSent = true;
|
|
778
775
|
try {
|
|
@@ -789,7 +786,7 @@ export class OpenClawClient {
|
|
|
789
786
|
}
|
|
790
787
|
reject(error);
|
|
791
788
|
};
|
|
792
|
-
const checkRunState = async () => {
|
|
789
|
+
const checkRunState = async (idleStillCurrent = () => true) => {
|
|
793
790
|
if (observationClosed || checkingState || Date.now() - lastStateCheckAt < 60_000)
|
|
794
791
|
return;
|
|
795
792
|
checkingState = true;
|
|
@@ -815,8 +812,10 @@ export class OpenClawClient {
|
|
|
815
812
|
else if (snapshot.status === "error" || snapshot.status === "timeout") {
|
|
816
813
|
if (snapshot.status === "timeout" || isWaitTimeout(String(snapshot.error || ""))) {
|
|
817
814
|
const status = await this.getSessionRuntimeStatus(targetSessionKey || sessionKey);
|
|
815
|
+
collectEvents();
|
|
818
816
|
if (!observationClosed && status.running) {
|
|
819
|
-
|
|
817
|
+
if (idleStillCurrent())
|
|
818
|
+
await handleTimeoutResult(new SessionWaitPaused(status, true));
|
|
820
819
|
return;
|
|
821
820
|
}
|
|
822
821
|
}
|
|
@@ -831,33 +830,102 @@ export class OpenClawClient {
|
|
|
831
830
|
checkingState = false;
|
|
832
831
|
}
|
|
833
832
|
};
|
|
833
|
+
// One clock for foreground silence and subsequent low-frequency observation.
|
|
834
|
+
// A generation fences status RPCs that complete after newer real activity.
|
|
835
|
+
let idleGeneration = 0;
|
|
834
836
|
const resetIdleTimer = (delayMs = timeoutMs, activity = true) => {
|
|
835
837
|
if (activity) {
|
|
836
838
|
if (!foregroundPaused)
|
|
837
839
|
waitNoticeSent = false;
|
|
838
840
|
quietNotified = false;
|
|
839
841
|
}
|
|
842
|
+
const generation = ++idleGeneration;
|
|
843
|
+
const idleStillCurrent = () => !observationClosed && generation === idleGeneration;
|
|
840
844
|
if (idleTimer)
|
|
841
845
|
clearTimeout(idleTimer);
|
|
842
846
|
if (observationClosed)
|
|
843
847
|
return;
|
|
844
848
|
idleTimer = setTimeout(async () => {
|
|
849
|
+
// Drain already-received frames before judging silence: the 50ms
|
|
850
|
+
// collection poll may otherwise run just after this deadline.
|
|
851
|
+
collectEvents();
|
|
852
|
+
if (!idleStillCurrent())
|
|
853
|
+
return;
|
|
845
854
|
if (!quietNotified) {
|
|
846
855
|
quietNotified = true;
|
|
847
856
|
console.warn(`[OpenClaw] quiet run ${runId}; checking Gateway state without stopping execution`);
|
|
848
857
|
}
|
|
849
|
-
await checkRunState();
|
|
850
|
-
if (!
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
858
|
+
await checkRunState(idleStillCurrent);
|
|
859
|
+
if (!idleStillCurrent())
|
|
860
|
+
return;
|
|
861
|
+
const status = await this.getSessionRuntimeStatus(targetSessionKey || sessionKey);
|
|
862
|
+
collectEvents();
|
|
863
|
+
if (!idleStillCurrent())
|
|
864
|
+
return;
|
|
865
|
+
// With a foreground callback, silence freezes presentation even when
|
|
866
|
+
// status is unknown/idle; never invent running. Without one, retain
|
|
867
|
+
// the published running-only typed outcome and background checks.
|
|
868
|
+
if (status.running || options?.onWaitPaused) {
|
|
869
|
+
await handleTimeoutResult(new SessionWaitPaused(status));
|
|
870
|
+
return;
|
|
856
871
|
}
|
|
857
|
-
|
|
858
|
-
resetIdleTimer(60_000, false);
|
|
872
|
+
resetIdleTimer(60_000, false);
|
|
859
873
|
}, delayMs);
|
|
860
874
|
};
|
|
875
|
+
// Activity filtering is deliberately separate from reply assembly/matching:
|
|
876
|
+
// legacy session fallback must keep delivering replies as before, but an
|
|
877
|
+
// unrelated/unidentified run must not extend this run's foreground wait.
|
|
878
|
+
const activityKeys = new Set();
|
|
879
|
+
const activityText = new Map();
|
|
880
|
+
const claimActivity = (value) => {
|
|
881
|
+
// Retain fingerprints rather than copies of cumulative/private text.
|
|
882
|
+
const key = createHash("sha256").update(value).digest("hex");
|
|
883
|
+
if (activityKeys.has(key))
|
|
884
|
+
return false;
|
|
885
|
+
activityKeys.add(key);
|
|
886
|
+
return true;
|
|
887
|
+
};
|
|
888
|
+
const hasEffectiveActivity = (ev) => {
|
|
889
|
+
if (!ev.runId || !activeRunIds.has(ev.runId))
|
|
890
|
+
return false;
|
|
891
|
+
const data = ev.data || {};
|
|
892
|
+
const run = String(ev.runId);
|
|
893
|
+
if (ev.stream === "tool" || (ev.stream === "item" && data.kind === "tool")) {
|
|
894
|
+
const phase = data.phase === "result" ? (data.isError || data.error ? "error" : "end") : data.phase;
|
|
895
|
+
if (phase !== "start" && phase !== "end" && phase !== "error")
|
|
896
|
+
return false;
|
|
897
|
+
// Canonical tool and legacy item mirrors share identity and phase.
|
|
898
|
+
// Without a call id, conservatively recognize each name/phase once.
|
|
899
|
+
const id = data.toolCallId || data.itemId || data.id || data.name || data.toolName;
|
|
900
|
+
return Boolean(id) && claimActivity(JSON.stringify([run, "tool", id, phase]));
|
|
901
|
+
}
|
|
902
|
+
if (ev.stream === "lifecycle" && data.phase === "start") {
|
|
903
|
+
return claimActivity(JSON.stringify([run, "start"]));
|
|
904
|
+
}
|
|
905
|
+
if (ev.stream === "assistant" || ev.stream === "chatDelta" || ev.stream === "transcriptAssistant") {
|
|
906
|
+
const fullText = typeof data.text === "string" ? data.text : "";
|
|
907
|
+
const chunk = fullText || data.deltaText || data.delta;
|
|
908
|
+
const replace = Boolean(fullText || data.replace);
|
|
909
|
+
if (typeof chunk !== "string" || !chunk.trim())
|
|
910
|
+
return false;
|
|
911
|
+
const source = JSON.stringify([run, ev.stream]);
|
|
912
|
+
// seq identifies repeated incremental frames, not progress by itself.
|
|
913
|
+
// No-seq identical fragments are ambiguous: do not let replay extend idle.
|
|
914
|
+
if (!claimActivity(JSON.stringify([source, ev.seq ?? null, replace, chunk])))
|
|
915
|
+
return false;
|
|
916
|
+
const previous = activityText.get(source) || "";
|
|
917
|
+
const next = replace ? chunk : previous + chunk;
|
|
918
|
+
activityText.set(source, next);
|
|
919
|
+
if (next === previous || (replace && previous.includes(next)))
|
|
920
|
+
return false;
|
|
921
|
+
// Replayed replacement snapshots and assistant/chat/transcript mirrors
|
|
922
|
+
// with the same accumulated text are not additional task progress.
|
|
923
|
+
return claimActivity(JSON.stringify([run, "text", next]));
|
|
924
|
+
}
|
|
925
|
+
// Usage, ticks, user receipts, status/wait responses and unknown streams
|
|
926
|
+
// are not verified task progress. Tool output is counted at completion.
|
|
927
|
+
return false;
|
|
928
|
+
};
|
|
861
929
|
const summarizeActivity = (ev) => {
|
|
862
930
|
if (ev.stream === "item") {
|
|
863
931
|
const data = ev.data || {};
|
|
@@ -920,10 +988,12 @@ export class OpenClawClient {
|
|
|
920
988
|
clearTimeout(genericChatErrorTimer);
|
|
921
989
|
resolve(finalText);
|
|
922
990
|
};
|
|
923
|
-
const
|
|
991
|
+
const collectEvents = () => {
|
|
992
|
+
if (observationClosed)
|
|
993
|
+
return;
|
|
924
994
|
// Gateway connection dropped mid-run (e.g. gateway restart): the run will
|
|
925
995
|
// never deliver a reply on this connection, so stop waiting instead of
|
|
926
|
-
// hanging until the
|
|
996
|
+
// hanging until the idle timeout. Failing fast lets the bridge
|
|
927
997
|
// release its busy lock so new messages are not stuck "waiting".
|
|
928
998
|
if (startedConnected && !this.connected) {
|
|
929
999
|
clearInterval(poller);
|
|
@@ -974,7 +1044,6 @@ export class OpenClawClient {
|
|
|
974
1044
|
bucket.splice(i, 1);
|
|
975
1045
|
if (!anchorSeen && isExpectedUserText(ev.data?.text || "")) {
|
|
976
1046
|
anchorSeen = true;
|
|
977
|
-
resetIdleTimer();
|
|
978
1047
|
for (const pendingEv of preAnchorEvents) {
|
|
979
1048
|
const pendingRunId = typeof pendingEv.runId === "string" ? pendingEv.runId : "";
|
|
980
1049
|
if (pendingRunId && pendingEv.stream === "lifecycle" && pendingEv.data?.phase === "start") {
|
|
@@ -1045,10 +1114,8 @@ export class OpenClawClient {
|
|
|
1045
1114
|
continue;
|
|
1046
1115
|
}
|
|
1047
1116
|
bucket.splice(i, 1);
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
// wall-clock timeout, so long tool-heavy tasks are not killed while active.
|
|
1051
|
-
resetIdleTimer();
|
|
1117
|
+
if (hasEffectiveActivity(ev))
|
|
1118
|
+
resetIdleTimer();
|
|
1052
1119
|
rememberActivity(ev);
|
|
1053
1120
|
// If more events arrive after a replay-invalid lifecycle end, that lifecycle
|
|
1054
1121
|
// was not terminal for the user-visible run. Keep waiting for the real final.
|
|
@@ -1115,7 +1182,7 @@ export class OpenClawClient {
|
|
|
1115
1182
|
}
|
|
1116
1183
|
// Finish scanning this batch first so an already-arrived real
|
|
1117
1184
|
// final/error wins over a stale waiting notice. No status RPC
|
|
1118
|
-
// (or ten-minute
|
|
1185
|
+
// (or ten-minute silence) is needed to confirm this wire fact.
|
|
1119
1186
|
if (options?.onWaitPaused)
|
|
1120
1187
|
void Promise.resolve().then(() => handleTimeoutResult(new SessionWaitPaused(normalizeSessionRuntimeStatus(undefined), false, "yield")));
|
|
1121
1188
|
continue;
|
|
@@ -1233,7 +1300,6 @@ export class OpenClawClient {
|
|
|
1233
1300
|
// back to this text.
|
|
1234
1301
|
if (this.isRecoverableAgentError(errText)) {
|
|
1235
1302
|
pendingRuntimeFailureText = `⚠️ Agent 未正常完成\n原因: ${errText}`;
|
|
1236
|
-
resetIdleTimer();
|
|
1237
1303
|
console.warn(`[OpenClaw] recoverable lifecycle error for runId=${evRunId || runId}; deferring (agent may auto-recover): ${errText.slice(0, 120)}`);
|
|
1238
1304
|
return;
|
|
1239
1305
|
}
|
|
@@ -1291,7 +1357,6 @@ export class OpenClawClient {
|
|
|
1291
1357
|
}
|
|
1292
1358
|
if (this.isRecoverableAgentError(errText)) {
|
|
1293
1359
|
pendingRuntimeFailureText = `⚠️ Agent 未正常完成\n原因: ${errText}`;
|
|
1294
|
-
resetIdleTimer();
|
|
1295
1360
|
console.warn(`[OpenClaw] recoverable chat error for runId=${evRunId || runId}; deferring: ${errText.slice(0, 120)}`);
|
|
1296
1361
|
bucket.splice(i, 1);
|
|
1297
1362
|
continue;
|
|
@@ -1323,7 +1388,8 @@ export class OpenClawClient {
|
|
|
1323
1388
|
}
|
|
1324
1389
|
}
|
|
1325
1390
|
}
|
|
1326
|
-
}
|
|
1391
|
+
};
|
|
1392
|
+
const poller = setInterval(collectEvents, 50);
|
|
1327
1393
|
const observationKey = targetSessionKey ? this.canonicalSessionKey(targetSessionKey) : "";
|
|
1328
1394
|
const checkAfterNoActiveRun = async () => {
|
|
1329
1395
|
await checkRunState();
|
|
@@ -1342,29 +1408,12 @@ export class OpenClawClient {
|
|
|
1342
1408
|
};
|
|
1343
1409
|
if (observationKey)
|
|
1344
1410
|
this.runStateChecks.set(observationKey, checkAfterNoActiveRun);
|
|
1345
|
-
// Fixed foreground budget: tool/assistant activity must not keep a card
|
|
1346
|
-
// refreshing indefinitely. The original result observer stays alive.
|
|
1347
|
-
if (options?.onWaitPaused) {
|
|
1348
|
-
foregroundWaitTimer = setTimeout(async () => {
|
|
1349
|
-
if (observationClosed || foregroundPaused)
|
|
1350
|
-
return;
|
|
1351
|
-
await checkRunState();
|
|
1352
|
-
if (observationClosed || foregroundPaused)
|
|
1353
|
-
return;
|
|
1354
|
-
const status = await this.getSessionRuntimeStatus(targetSessionKey || sessionKey);
|
|
1355
|
-
if (!observationClosed && !foregroundPaused)
|
|
1356
|
-
await handleTimeoutResult(new SessionWaitPaused(status));
|
|
1357
|
-
}, timeoutMs);
|
|
1358
|
-
foregroundWaitTimer.unref?.();
|
|
1359
|
-
}
|
|
1360
1411
|
// Bound local resources without cancelling an execution we cannot prove stuck.
|
|
1361
1412
|
const observationHorizon = setTimeout(() => reject(new Error("LMA 本地监听达到 24 小时上限,结果未确认;未发送停止请求,请检查原运行。")), 24 * 60 * 60_000);
|
|
1362
1413
|
observationHorizon.unref?.();
|
|
1363
1414
|
cleanupObservation = () => {
|
|
1364
1415
|
observationClosed = true;
|
|
1365
1416
|
clearTimeout(idleTimer);
|
|
1366
|
-
if (foregroundWaitTimer)
|
|
1367
|
-
clearTimeout(foregroundWaitTimer);
|
|
1368
1417
|
clearTimeout(observationHorizon);
|
|
1369
1418
|
clearInterval(poller);
|
|
1370
1419
|
if (chatFinalTimer)
|
package/dist/session-status.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/** Consecutive effective-activity silence, not a total foreground lifetime.
|
|
2
|
+
* Starts with accepted-request collection; does not change Gateway execution limits. */
|
|
2
3
|
export declare const FOREGROUND_WAIT_MS: number;
|
|
3
4
|
export type SessionRuntimeStatus = {
|
|
4
5
|
status: string;
|
package/dist/session-status.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/** Consecutive effective-activity silence, not a total foreground lifetime.
|
|
2
|
+
* Starts with accepted-request collection; does not change Gateway execution limits. */
|
|
2
3
|
export const FOREGROUND_WAIT_MS = 10 * 60_000;
|
|
3
4
|
export const isWaitTimeout = (text) => /\btimeout\b|timed\s*out|超时/i.test(text) && !/invalid|validation|schema|malformed|permission denied|forbidden|unauthorized|authentication failed|quota|rate limit|无效参数|参数错误|校验失败/i.test(text);
|
|
4
5
|
export function normalizeSessionRuntimeStatus(value) {
|
|
@@ -35,7 +36,7 @@ export class SessionWaitPaused extends Error {
|
|
|
35
36
|
? "⏳ 本轮已转入后台等待;若有后续结果,将继续在此回复。未发送停止请求,也未自动重发请求。"
|
|
36
37
|
: reportedTimeout
|
|
37
38
|
? "⏳ 本轮返回了超时,LMA 已暂停本次等待,但不据此认定整个会话已经结束。会话状态见下方;你可以继续等待后续结果。"
|
|
38
|
-
: "⏳
|
|
39
|
+
: "⏳ 本轮连续无有效进展的等待时间已到,LMA 暂停本轮等待,先返回会话状态供你判断。\n没有因此停止运行或自动重发本次请求;你可以继续等待后续结果。");
|
|
39
40
|
this.snapshot = snapshot;
|
|
40
41
|
this.reason = reason;
|
|
41
42
|
this.name = "SessionWaitPaused";
|