mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 11:10:45 +00:00
refactor: share thread binding lifecycle
This commit is contained in:
@@ -1,8 +1,17 @@
|
||||
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
||||
import { normalizeAccountId } from "../routing/session-key.js";
|
||||
import { normalizeLowercaseStringOrEmpty } from "../shared/string-coerce.js";
|
||||
import {
|
||||
resolveThreadBindingLifecycle as resolveSharedThreadBindingLifecycle,
|
||||
type ThreadBindingLifecycleRecord,
|
||||
} from "../shared/thread-binding-lifecycle.js";
|
||||
import { getChannelPlugin } from "./plugins/index.js";
|
||||
|
||||
export {
|
||||
resolveThreadBindingLifecycle,
|
||||
type ThreadBindingLifecycleRecord,
|
||||
} from "../shared/thread-binding-lifecycle.js";
|
||||
|
||||
const DEFAULT_THREAD_BINDING_IDLE_HOURS = 24;
|
||||
const DEFAULT_THREAD_BINDING_MAX_AGE_HOURS = 0;
|
||||
|
||||
@@ -97,56 +106,12 @@ export function resolveThreadBindingMaxAgeMs(params: {
|
||||
return Math.floor(maxAgeHours * 60 * 60 * 1000);
|
||||
}
|
||||
|
||||
type ThreadBindingLifecycleRecord = {
|
||||
boundAt: number;
|
||||
lastActivityAt: number;
|
||||
idleTimeoutMs?: number;
|
||||
maxAgeMs?: number;
|
||||
};
|
||||
|
||||
export function resolveThreadBindingLifecycle(params: {
|
||||
record: ThreadBindingLifecycleRecord;
|
||||
defaultIdleTimeoutMs: number;
|
||||
defaultMaxAgeMs: number;
|
||||
}): {
|
||||
expiresAt?: number;
|
||||
reason?: "idle-expired" | "max-age-expired";
|
||||
} {
|
||||
const idleTimeoutMs =
|
||||
typeof params.record.idleTimeoutMs === "number"
|
||||
? Math.max(0, Math.floor(params.record.idleTimeoutMs))
|
||||
: params.defaultIdleTimeoutMs;
|
||||
const maxAgeMs =
|
||||
typeof params.record.maxAgeMs === "number"
|
||||
? Math.max(0, Math.floor(params.record.maxAgeMs))
|
||||
: params.defaultMaxAgeMs;
|
||||
|
||||
const inactivityExpiresAt =
|
||||
idleTimeoutMs > 0
|
||||
? Math.max(params.record.lastActivityAt, params.record.boundAt) + idleTimeoutMs
|
||||
: undefined;
|
||||
const maxAgeExpiresAt = maxAgeMs > 0 ? params.record.boundAt + maxAgeMs : undefined;
|
||||
|
||||
if (inactivityExpiresAt != null && maxAgeExpiresAt != null) {
|
||||
return inactivityExpiresAt <= maxAgeExpiresAt
|
||||
? { expiresAt: inactivityExpiresAt, reason: "idle-expired" }
|
||||
: { expiresAt: maxAgeExpiresAt, reason: "max-age-expired" };
|
||||
}
|
||||
if (inactivityExpiresAt != null) {
|
||||
return { expiresAt: inactivityExpiresAt, reason: "idle-expired" };
|
||||
}
|
||||
if (maxAgeExpiresAt != null) {
|
||||
return { expiresAt: maxAgeExpiresAt, reason: "max-age-expired" };
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
export function resolveThreadBindingEffectiveExpiresAt(params: {
|
||||
record: ThreadBindingLifecycleRecord;
|
||||
defaultIdleTimeoutMs: number;
|
||||
defaultMaxAgeMs: number;
|
||||
}): number | undefined {
|
||||
return resolveThreadBindingLifecycle(params).expiresAt;
|
||||
return resolveSharedThreadBindingLifecycle(params).expiresAt;
|
||||
}
|
||||
|
||||
export function resolveThreadBindingsEnabled(params: {
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
export { resolveThreadBindingFarewellText } from "../channels/thread-bindings-messages.js";
|
||||
export {
|
||||
resolveThreadBindingLifecycle,
|
||||
type ThreadBindingLifecycleRecord,
|
||||
} from "../shared/thread-binding-lifecycle.js";
|
||||
export {
|
||||
registerSessionBindingAdapter,
|
||||
unregisterSessionBindingAdapter,
|
||||
@@ -6,47 +10,3 @@ export {
|
||||
type SessionBindingAdapter,
|
||||
type SessionBindingRecord,
|
||||
} from "../infra/outbound/session-binding-service.js";
|
||||
|
||||
type ThreadBindingLifecycleRecord = {
|
||||
boundAt: number;
|
||||
lastActivityAt: number;
|
||||
idleTimeoutMs?: number;
|
||||
maxAgeMs?: number;
|
||||
};
|
||||
|
||||
export function resolveThreadBindingLifecycle(params: {
|
||||
record: ThreadBindingLifecycleRecord;
|
||||
defaultIdleTimeoutMs: number;
|
||||
defaultMaxAgeMs: number;
|
||||
}): {
|
||||
expiresAt?: number;
|
||||
reason?: "idle-expired" | "max-age-expired";
|
||||
} {
|
||||
const idleTimeoutMs =
|
||||
typeof params.record.idleTimeoutMs === "number"
|
||||
? Math.max(0, Math.floor(params.record.idleTimeoutMs))
|
||||
: params.defaultIdleTimeoutMs;
|
||||
const maxAgeMs =
|
||||
typeof params.record.maxAgeMs === "number"
|
||||
? Math.max(0, Math.floor(params.record.maxAgeMs))
|
||||
: params.defaultMaxAgeMs;
|
||||
|
||||
const inactivityExpiresAt =
|
||||
idleTimeoutMs > 0
|
||||
? Math.max(params.record.lastActivityAt, params.record.boundAt) + idleTimeoutMs
|
||||
: undefined;
|
||||
const maxAgeExpiresAt = maxAgeMs > 0 ? params.record.boundAt + maxAgeMs : undefined;
|
||||
|
||||
if (inactivityExpiresAt != null && maxAgeExpiresAt != null) {
|
||||
return inactivityExpiresAt <= maxAgeExpiresAt
|
||||
? { expiresAt: inactivityExpiresAt, reason: "idle-expired" }
|
||||
: { expiresAt: maxAgeExpiresAt, reason: "max-age-expired" };
|
||||
}
|
||||
if (inactivityExpiresAt != null) {
|
||||
return { expiresAt: inactivityExpiresAt, reason: "idle-expired" };
|
||||
}
|
||||
if (maxAgeExpiresAt != null) {
|
||||
return { expiresAt: maxAgeExpiresAt, reason: "max-age-expired" };
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
42
src/shared/thread-binding-lifecycle.test.ts
Normal file
42
src/shared/thread-binding-lifecycle.test.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { resolveThreadBindingLifecycle } from "./thread-binding-lifecycle.js";
|
||||
|
||||
describe("resolveThreadBindingLifecycle", () => {
|
||||
it("prefers the earliest idle or max-age expiration", () => {
|
||||
expect(
|
||||
resolveThreadBindingLifecycle({
|
||||
record: {
|
||||
boundAt: 100,
|
||||
lastActivityAt: 300,
|
||||
idleTimeoutMs: 50,
|
||||
maxAgeMs: 1_000,
|
||||
},
|
||||
defaultIdleTimeoutMs: 24 * 60 * 60 * 1000,
|
||||
defaultMaxAgeMs: 0,
|
||||
}),
|
||||
).toEqual({ expiresAt: 350, reason: "idle-expired" });
|
||||
|
||||
expect(
|
||||
resolveThreadBindingLifecycle({
|
||||
record: {
|
||||
boundAt: 100,
|
||||
lastActivityAt: 300,
|
||||
idleTimeoutMs: 1_000,
|
||||
maxAgeMs: 150,
|
||||
},
|
||||
defaultIdleTimeoutMs: 24 * 60 * 60 * 1000,
|
||||
defaultMaxAgeMs: 0,
|
||||
}),
|
||||
).toEqual({ expiresAt: 250, reason: "max-age-expired" });
|
||||
});
|
||||
|
||||
it("uses defaults when record-level timeouts are absent", () => {
|
||||
expect(
|
||||
resolveThreadBindingLifecycle({
|
||||
record: { boundAt: 100, lastActivityAt: 300 },
|
||||
defaultIdleTimeoutMs: 200,
|
||||
defaultMaxAgeMs: 0,
|
||||
}),
|
||||
).toEqual({ expiresAt: 500, reason: "idle-expired" });
|
||||
});
|
||||
});
|
||||
43
src/shared/thread-binding-lifecycle.ts
Normal file
43
src/shared/thread-binding-lifecycle.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
export type ThreadBindingLifecycleRecord = {
|
||||
boundAt: number;
|
||||
lastActivityAt: number;
|
||||
idleTimeoutMs?: number;
|
||||
maxAgeMs?: number;
|
||||
};
|
||||
|
||||
export function resolveThreadBindingLifecycle(params: {
|
||||
record: ThreadBindingLifecycleRecord;
|
||||
defaultIdleTimeoutMs: number;
|
||||
defaultMaxAgeMs: number;
|
||||
}): {
|
||||
expiresAt?: number;
|
||||
reason?: "idle-expired" | "max-age-expired";
|
||||
} {
|
||||
const idleTimeoutMs =
|
||||
typeof params.record.idleTimeoutMs === "number"
|
||||
? Math.max(0, Math.floor(params.record.idleTimeoutMs))
|
||||
: params.defaultIdleTimeoutMs;
|
||||
const maxAgeMs =
|
||||
typeof params.record.maxAgeMs === "number"
|
||||
? Math.max(0, Math.floor(params.record.maxAgeMs))
|
||||
: params.defaultMaxAgeMs;
|
||||
|
||||
const inactivityExpiresAt =
|
||||
idleTimeoutMs > 0
|
||||
? Math.max(params.record.lastActivityAt, params.record.boundAt) + idleTimeoutMs
|
||||
: undefined;
|
||||
const maxAgeExpiresAt = maxAgeMs > 0 ? params.record.boundAt + maxAgeMs : undefined;
|
||||
|
||||
if (inactivityExpiresAt != null && maxAgeExpiresAt != null) {
|
||||
return inactivityExpiresAt <= maxAgeExpiresAt
|
||||
? { expiresAt: inactivityExpiresAt, reason: "idle-expired" }
|
||||
: { expiresAt: maxAgeExpiresAt, reason: "max-age-expired" };
|
||||
}
|
||||
if (inactivityExpiresAt != null) {
|
||||
return { expiresAt: inactivityExpiresAt, reason: "idle-expired" };
|
||||
}
|
||||
if (maxAgeExpiresAt != null) {
|
||||
return { expiresAt: maxAgeExpiresAt, reason: "max-age-expired" };
|
||||
}
|
||||
return {};
|
||||
}
|
||||
Reference in New Issue
Block a user