mirror of
https://github.com/openclaw/openclaw.git
synced 2026-02-19 18:39:20 -05:00
feat: backward compat for resetByType.dm config key
This commit is contained in:
@@ -2760,7 +2760,7 @@ Controls session scoping, reset policy, reset triggers, and where the session st
|
||||
},
|
||||
resetByType: {
|
||||
thread: { mode: "daily", atHour: 4 },
|
||||
dm: { mode: "idle", idleMinutes: 240 },
|
||||
direct: { mode: "idle", idleMinutes: 240 },
|
||||
group: { mode: "idle", idleMinutes: 120 },
|
||||
},
|
||||
resetTriggers: ["/new", "/reset"],
|
||||
@@ -2797,7 +2797,7 @@ Fields:
|
||||
- `mode`: `daily` or `idle` (default: `daily` when `reset` is present).
|
||||
- `atHour`: local hour (0-23) for the daily reset boundary.
|
||||
- `idleMinutes`: sliding idle window in minutes. When daily + idle are both configured, whichever expires first wins.
|
||||
- `resetByType`: per-session overrides for `dm`, `group`, and `thread`.
|
||||
- `resetByType`: per-session overrides for `direct`, `group`, and `thread`. Legacy `dm` key is accepted as an alias for `direct`.
|
||||
- If you only set legacy `session.idleMinutes` without any `reset`/`resetByType`, OpenClaw stays in idle-only mode for backward compatibility.
|
||||
- `heartbeatIdleMinutes`: optional idle override for heartbeat checks (daily reset still applies when enabled).
|
||||
- `agentToAgent.maxPingPongTurns`: max reply-back turns between requester/target (0–5, default 5).
|
||||
|
||||
72
src/config/sessions/reset.test.ts
Normal file
72
src/config/sessions/reset.test.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import type { SessionConfig } from "../types.base.js";
|
||||
import { resolveSessionResetPolicy } from "./reset.js";
|
||||
|
||||
describe("resolveSessionResetPolicy", () => {
|
||||
describe("backward compatibility: resetByType.dm → direct", () => {
|
||||
it("uses resetByType.direct when available", () => {
|
||||
const sessionCfg = {
|
||||
resetByType: {
|
||||
direct: { mode: "idle" as const, idleMinutes: 30 },
|
||||
},
|
||||
} satisfies SessionConfig;
|
||||
|
||||
const policy = resolveSessionResetPolicy({
|
||||
sessionCfg,
|
||||
resetType: "direct",
|
||||
});
|
||||
|
||||
expect(policy.mode).toBe("idle");
|
||||
expect(policy.idleMinutes).toBe(30);
|
||||
});
|
||||
|
||||
it("falls back to resetByType.dm (legacy) when direct is missing", () => {
|
||||
// Simulating legacy config with "dm" key instead of "direct"
|
||||
const sessionCfg = {
|
||||
resetByType: {
|
||||
dm: { mode: "idle" as const, idleMinutes: 45 },
|
||||
},
|
||||
} as unknown as SessionConfig;
|
||||
|
||||
const policy = resolveSessionResetPolicy({
|
||||
sessionCfg,
|
||||
resetType: "direct",
|
||||
});
|
||||
|
||||
expect(policy.mode).toBe("idle");
|
||||
expect(policy.idleMinutes).toBe(45);
|
||||
});
|
||||
|
||||
it("prefers resetByType.direct over resetByType.dm when both present", () => {
|
||||
const sessionCfg = {
|
||||
resetByType: {
|
||||
direct: { mode: "daily" as const },
|
||||
dm: { mode: "idle" as const, idleMinutes: 99 },
|
||||
},
|
||||
} as unknown as SessionConfig;
|
||||
|
||||
const policy = resolveSessionResetPolicy({
|
||||
sessionCfg,
|
||||
resetType: "direct",
|
||||
});
|
||||
|
||||
expect(policy.mode).toBe("daily");
|
||||
});
|
||||
|
||||
it("does not use dm fallback for group/thread types", () => {
|
||||
const sessionCfg = {
|
||||
resetByType: {
|
||||
dm: { mode: "idle" as const, idleMinutes: 45 },
|
||||
},
|
||||
} as unknown as SessionConfig;
|
||||
|
||||
const groupPolicy = resolveSessionResetPolicy({
|
||||
sessionCfg,
|
||||
resetType: "group",
|
||||
});
|
||||
|
||||
// Should use default mode since group has no config and dm doesn't apply
|
||||
expect(groupPolicy.mode).toBe("daily");
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -88,7 +88,13 @@ export function resolveSessionResetPolicy(params: {
|
||||
}): SessionResetPolicy {
|
||||
const sessionCfg = params.sessionCfg;
|
||||
const baseReset = params.resetOverride ?? sessionCfg?.reset;
|
||||
const typeReset = params.resetOverride ? undefined : sessionCfg?.resetByType?.[params.resetType];
|
||||
// Backward compat: accept legacy "dm" key as alias for "direct"
|
||||
const typeReset = params.resetOverride
|
||||
? undefined
|
||||
: (sessionCfg?.resetByType?.[params.resetType] ??
|
||||
(params.resetType === "direct"
|
||||
? (sessionCfg?.resetByType as { dm?: SessionResetConfig } | undefined)?.dm
|
||||
: undefined));
|
||||
const hasExplicitReset = Boolean(baseReset || sessionCfg?.resetByType);
|
||||
const legacyIdleMinutes = params.resetOverride ? undefined : sessionCfg?.idleMinutes;
|
||||
const mode =
|
||||
|
||||
@@ -72,6 +72,8 @@ export type SessionResetConfig = {
|
||||
};
|
||||
export type SessionResetByTypeConfig = {
|
||||
direct?: SessionResetConfig;
|
||||
/** @deprecated Use `direct` instead. Kept for backward compatibility. */
|
||||
dm?: SessionResetConfig;
|
||||
group?: SessionResetConfig;
|
||||
thread?: SessionResetConfig;
|
||||
};
|
||||
|
||||
@@ -22,7 +22,13 @@ export const BindingsSchema = z
|
||||
accountId: z.string().optional(),
|
||||
peer: z
|
||||
.object({
|
||||
kind: z.union([z.literal("direct"), z.literal("group"), z.literal("channel")]),
|
||||
kind: z.union([
|
||||
z.literal("direct"),
|
||||
z.literal("group"),
|
||||
z.literal("channel"),
|
||||
/** @deprecated Use `direct` instead. Kept for backward compatibility. */
|
||||
z.literal("dm"),
|
||||
]),
|
||||
id: z.string(),
|
||||
})
|
||||
.strict()
|
||||
|
||||
@@ -378,7 +378,13 @@ export const MediaUnderstandingScopeSchema = z
|
||||
.object({
|
||||
channel: z.string().optional(),
|
||||
chatType: z
|
||||
.union([z.literal("direct"), z.literal("group"), z.literal("channel")])
|
||||
.union([
|
||||
z.literal("direct"),
|
||||
z.literal("group"),
|
||||
z.literal("channel"),
|
||||
/** @deprecated Use `direct` instead. Kept for backward compatibility. */
|
||||
z.literal("dm"),
|
||||
])
|
||||
.optional(),
|
||||
keyPrefix: z.string().optional(),
|
||||
})
|
||||
|
||||
@@ -27,7 +27,13 @@ export const SessionSendPolicySchema = z
|
||||
.object({
|
||||
channel: z.string().optional(),
|
||||
chatType: z
|
||||
.union([z.literal("direct"), z.literal("group"), z.literal("channel")])
|
||||
.union([
|
||||
z.literal("direct"),
|
||||
z.literal("group"),
|
||||
z.literal("channel"),
|
||||
/** @deprecated Use `direct` instead. Kept for backward compatibility. */
|
||||
z.literal("dm"),
|
||||
])
|
||||
.optional(),
|
||||
keyPrefix: z.string().optional(),
|
||||
})
|
||||
@@ -58,6 +64,8 @@ export const SessionSchema = z
|
||||
resetByType: z
|
||||
.object({
|
||||
direct: SessionResetConfigSchema.optional(),
|
||||
/** @deprecated Use `direct` instead. Kept for backward compatibility. */
|
||||
dm: SessionResetConfigSchema.optional(),
|
||||
group: SessionResetConfigSchema.optional(),
|
||||
thread: SessionResetConfigSchema.optional(),
|
||||
})
|
||||
|
||||
@@ -409,3 +409,29 @@ describe("parentPeer binding inheritance (thread support)", () => {
|
||||
expect(route.matchedBy).toBe("default");
|
||||
});
|
||||
});
|
||||
|
||||
describe("backward compatibility: peer.kind dm → direct", () => {
|
||||
test("legacy dm in config matches runtime direct peer", () => {
|
||||
const cfg: OpenClawConfig = {
|
||||
bindings: [
|
||||
{
|
||||
agentId: "alex",
|
||||
match: {
|
||||
channel: "whatsapp",
|
||||
// Legacy config uses "dm" instead of "direct"
|
||||
peer: { kind: "dm", id: "+15551234567" },
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
const route = resolveAgentRoute({
|
||||
cfg,
|
||||
channel: "whatsapp",
|
||||
accountId: null,
|
||||
// Runtime uses canonical "direct"
|
||||
peer: { kind: "direct", id: "+15551234567" },
|
||||
});
|
||||
expect(route.agentId).toBe("alex");
|
||||
expect(route.matchedBy).toBe("binding.peer");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { ChatType } from "../channels/chat-type.js";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import { resolveDefaultAgentId } from "../agents/agent-scope.js";
|
||||
import { normalizeChatType } from "../channels/chat-type.js";
|
||||
import { listBindings } from "./bindings.js";
|
||||
import {
|
||||
buildAgentMainSessionKey,
|
||||
@@ -139,7 +140,8 @@ function matchesPeer(
|
||||
if (!m) {
|
||||
return false;
|
||||
}
|
||||
const kind = normalizeToken(m.kind);
|
||||
// Backward compat: normalize "dm" to "direct" in config match rules
|
||||
const kind = normalizeChatType(m.kind);
|
||||
const id = normalizeId(m.id);
|
||||
if (!kind || !id) {
|
||||
return false;
|
||||
|
||||
Reference in New Issue
Block a user