Discord: thread bindings idle + max-age lifecycle (#27845) (thanks @osolmaz)

* refactor discord thread bindings to idle and max-age lifecycle

* fix: migrate legacy thread binding expiry and reduce hot-path disk writes

* refactor: remove remaining thread-binding ttl legacy paths

* fix: harden thread-binding lifecycle persistence

* Discord: fix thread binding types in message/reply paths

* Infra: handle win32 unknown inode in file identity checks

* Infra: relax win32 guarded-open identity checks

* Config: migrate threadBindings ttlHours to idleHours

* Revert "Infra: relax win32 guarded-open identity checks"

This reverts commit de94126771.

* Revert "Infra: handle win32 unknown inode in file identity checks"

This reverts commit 96fc5ddfb3.

* Discord: re-read live binding state before sweep unbind

* fix: add changelog note for thread binding lifecycle update (#27845) (thanks @osolmaz)

---------

Co-authored-by: Onur Solmaz <onur@textcortex.com>
This commit is contained in:
Onur Solmaz
2026-02-27 10:02:39 +01:00
committed by GitHub
parent 0fb7add7d6
commit a7929abad8
45 changed files with 1656 additions and 402 deletions

View File

@@ -2,11 +2,13 @@ import type { OpenClawConfig } from "../config/config.js";
import { normalizeAccountId } from "../routing/session-key.js";
export const DISCORD_THREAD_BINDING_CHANNEL = "discord";
const DEFAULT_THREAD_BINDING_TTL_HOURS = 24;
const DEFAULT_THREAD_BINDING_IDLE_HOURS = 24;
const DEFAULT_THREAD_BINDING_MAX_AGE_HOURS = 0;
type SessionThreadBindingsConfigShape = {
enabled?: unknown;
ttlHours?: unknown;
idleHours?: unknown;
maxAgeHours?: unknown;
spawnSubagentSessions?: unknown;
spawnAcpSessions?: unknown;
};
@@ -38,7 +40,7 @@ function normalizeBoolean(value: unknown): boolean | undefined {
return value;
}
function normalizeThreadBindingTtlHours(raw: unknown): number | undefined {
function normalizeThreadBindingHours(raw: unknown): number | undefined {
if (typeof raw !== "number" || !Number.isFinite(raw)) {
return undefined;
}
@@ -48,15 +50,26 @@ function normalizeThreadBindingTtlHours(raw: unknown): number | undefined {
return raw;
}
export function resolveThreadBindingSessionTtlMs(params: {
channelTtlHoursRaw: unknown;
sessionTtlHoursRaw: unknown;
export function resolveThreadBindingIdleTimeoutMs(params: {
channelIdleHoursRaw: unknown;
sessionIdleHoursRaw: unknown;
}): number {
const ttlHours =
normalizeThreadBindingTtlHours(params.channelTtlHoursRaw) ??
normalizeThreadBindingTtlHours(params.sessionTtlHoursRaw) ??
DEFAULT_THREAD_BINDING_TTL_HOURS;
return Math.floor(ttlHours * 60 * 60 * 1000);
const idleHours =
normalizeThreadBindingHours(params.channelIdleHoursRaw) ??
normalizeThreadBindingHours(params.sessionIdleHoursRaw) ??
DEFAULT_THREAD_BINDING_IDLE_HOURS;
return Math.floor(idleHours * 60 * 60 * 1000);
}
export function resolveThreadBindingMaxAgeMs(params: {
channelMaxAgeHoursRaw: unknown;
sessionMaxAgeHoursRaw: unknown;
}): number {
const maxAgeHours =
normalizeThreadBindingHours(params.channelMaxAgeHoursRaw) ??
normalizeThreadBindingHours(params.sessionMaxAgeHoursRaw) ??
DEFAULT_THREAD_BINDING_MAX_AGE_HOURS;
return Math.floor(maxAgeHours * 60 * 60 * 1000);
}
export function resolveThreadBindingsEnabled(params: {
@@ -124,7 +137,7 @@ export function resolveThreadBindingSpawnPolicy(params: {
};
}
export function resolveThreadBindingSessionTtlMsForChannel(params: {
export function resolveThreadBindingIdleTimeoutMsForChannel(params: {
cfg: OpenClawConfig;
channel: string;
accountId?: string;
@@ -136,9 +149,27 @@ export function resolveThreadBindingSessionTtlMsForChannel(params: {
channel,
accountId,
});
return resolveThreadBindingSessionTtlMs({
channelTtlHoursRaw: account?.ttlHours ?? root?.ttlHours,
sessionTtlHoursRaw: params.cfg.session?.threadBindings?.ttlHours,
return resolveThreadBindingIdleTimeoutMs({
channelIdleHoursRaw: account?.idleHours ?? root?.idleHours,
sessionIdleHoursRaw: params.cfg.session?.threadBindings?.idleHours,
});
}
export function resolveThreadBindingMaxAgeMsForChannel(params: {
cfg: OpenClawConfig;
channel: string;
accountId?: string;
}): number {
const channel = normalizeChannelId(params.channel);
const accountId = normalizeAccountId(params.accountId);
const { root, account } = resolveChannelThreadBindings({
cfg: params.cfg,
channel,
accountId,
});
return resolveThreadBindingMaxAgeMs({
channelMaxAgeHoursRaw: account?.maxAgeHours ?? root?.maxAgeHours,
sessionMaxAgeHoursRaw: params.cfg.session?.threadBindings?.maxAgeHours,
});
}