mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-05 11:02:54 +00:00
fix(auto-reply): bound session lifecycle expiries
This commit is contained in:
@@ -570,7 +570,7 @@ describe("/session idle and /session max-age", () => {
|
||||
expect(result?.reply?.text).toContain("2026-02-20T02:00:00.000Z");
|
||||
});
|
||||
|
||||
it("falls back when active idle timeout expiry is Date-invalid", async () => {
|
||||
it("falls back to bind time when idle activity timestamp is out of range", async () => {
|
||||
vi.useFakeTimers();
|
||||
vi.setSystemTime(new Date("2026-02-20T00:00:00.000Z"));
|
||||
|
||||
@@ -586,7 +586,26 @@ describe("/session idle and /session max-age", () => {
|
||||
);
|
||||
|
||||
const result = await handleSessionCommand(createThreadCommandParams("/session idle"), true);
|
||||
expect(result?.reply?.text).toBe("ℹ️ Idle timeout active (2h, next auto-unfocus at n/a).");
|
||||
expect(result?.reply?.text).toContain("Idle timeout active (2h");
|
||||
expect(result?.reply?.text).toContain("2026-02-20T02:00:00.000Z");
|
||||
});
|
||||
|
||||
it("treats overflowed idle timeout metadata as disabled", async () => {
|
||||
hoisted.sessionBindingResolveByConversationMock.mockReturnValue(
|
||||
createThreadBinding({
|
||||
metadata: {
|
||||
boundBy: "user-1",
|
||||
lastActivityAt: Date.now(),
|
||||
idleTimeoutMs: Number.MAX_SAFE_INTEGER,
|
||||
maxAgeMs: 0,
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
const result = await handleSessionCommand(createThreadCommandParams("/session idle"), true);
|
||||
expect(result?.reply?.text).toBe(
|
||||
"ℹ️ Idle timeout is currently disabled for this focused session.",
|
||||
);
|
||||
});
|
||||
|
||||
it("sets max age for the focused thread-chat session", async () => {
|
||||
|
||||
@@ -27,6 +27,10 @@ import {
|
||||
} from "../../infra/restart-sentinel.js";
|
||||
import { scheduleGatewaySigusr1Restart, triggerOpenClawRestart } from "../../infra/restart.js";
|
||||
import { loadCostUsageSummary, loadSessionCostSummary } from "../../infra/session-cost-usage.js";
|
||||
import {
|
||||
asDateTimestampMs,
|
||||
resolveExpiresAtMsFromDurationMs,
|
||||
} from "../../shared/number-coercion.js";
|
||||
import { formatTokenCount, formatUsd } from "../../utils/usage-format.js";
|
||||
import { parseActivationCommand } from "../group-activation.js";
|
||||
import { parseSendPolicyCommand } from "../send-policy.js";
|
||||
@@ -104,13 +108,19 @@ function resolveSessionBindingDurationMs(
|
||||
}
|
||||
|
||||
function resolveSessionBindingLastActivityAt(binding: SessionBindingRecord): number {
|
||||
const raw = binding.metadata?.lastActivityAt;
|
||||
if (typeof raw !== "number" || !Number.isFinite(raw)) {
|
||||
const raw = asDateTimestampMs(binding.metadata?.lastActivityAt);
|
||||
if (raw === undefined) {
|
||||
return binding.boundAt;
|
||||
}
|
||||
return Math.max(Math.floor(raw), binding.boundAt);
|
||||
}
|
||||
|
||||
function resolveSessionBindingExpiryAt(baseMs: number, durationMs: number): number | undefined {
|
||||
return durationMs > 0
|
||||
? resolveExpiresAtMsFromDurationMs(durationMs, { nowMs: baseMs })
|
||||
: undefined;
|
||||
}
|
||||
|
||||
function resolveSessionBindingBoundBy(binding: SessionBindingRecord): string {
|
||||
const raw = binding.metadata?.boundBy;
|
||||
return normalizeOptionalString(raw) ?? "";
|
||||
@@ -177,7 +187,10 @@ function resolveUpdatedBindingExpiry(params: {
|
||||
if (idleTimeoutMs <= 0) {
|
||||
return undefined;
|
||||
}
|
||||
return Math.max(binding.lastActivityAt, binding.boundAt) + idleTimeoutMs;
|
||||
return resolveSessionBindingExpiryAt(
|
||||
Math.max(binding.lastActivityAt, binding.boundAt),
|
||||
idleTimeoutMs,
|
||||
);
|
||||
}
|
||||
|
||||
const maxAgeMs =
|
||||
@@ -187,7 +200,7 @@ function resolveUpdatedBindingExpiry(params: {
|
||||
if (maxAgeMs <= 0) {
|
||||
return undefined;
|
||||
}
|
||||
return binding.boundAt + maxAgeMs;
|
||||
return resolveSessionBindingExpiryAt(binding.boundAt, maxAgeMs);
|
||||
})
|
||||
.filter((expiresAt): expiresAt is number => typeof expiresAt === "number");
|
||||
|
||||
@@ -537,12 +550,12 @@ export const handleSessionCommand: CommandHandler = async (params, allowTextComm
|
||||
"idleTimeoutMs",
|
||||
24 * 60 * 60 * 1000,
|
||||
);
|
||||
const idleExpiresAt =
|
||||
idleTimeoutMs > 0
|
||||
? resolveSessionBindingLastActivityAt(activeBinding) + idleTimeoutMs
|
||||
: undefined;
|
||||
const idleExpiresAt = resolveSessionBindingExpiryAt(
|
||||
resolveSessionBindingLastActivityAt(activeBinding),
|
||||
idleTimeoutMs,
|
||||
);
|
||||
const maxAgeMs = resolveSessionBindingDurationMs(activeBinding, "maxAgeMs", 0);
|
||||
const maxAgeExpiresAt = maxAgeMs > 0 ? activeBinding.boundAt + maxAgeMs : undefined;
|
||||
const maxAgeExpiresAt = resolveSessionBindingExpiryAt(activeBinding.boundAt, maxAgeMs);
|
||||
|
||||
const durationArgRaw = tokens.slice(1).join("");
|
||||
if (!durationArgRaw) {
|
||||
|
||||
Reference in New Issue
Block a user