refactor: remove runtime legacy session hooks

This commit is contained in:
Peter Steinberger
2026-05-09 18:32:45 +01:00
parent 830eb06bce
commit faaee3e9d8
13 changed files with 25 additions and 148 deletions

View File

@@ -13,7 +13,6 @@ export {
unsupportedSecretRefSurfacePatterns,
collectUnsupportedSecretRefConfigCandidates,
} from "./src/security-contract.js";
export { deriveLegacySessionChatType } from "./src/session-contract.js";
export type {
DiscordInteractiveHandlerContext,
DiscordInteractiveHandlerRegistration,

View File

@@ -1,3 +0,0 @@
export function deriveLegacySessionChatType(sessionKey: string): "channel" | undefined {
return /^discord:(?:[^:]+:)?guild-[^:]+:channel-[^:]+$/.test(sessionKey) ? "channel" : undefined;
}

View File

@@ -29,7 +29,6 @@ import {
unsupportedSecretRefSurfacePatterns,
} from "./security-contract.js";
import { discordSecurityAdapter } from "./security.js";
import { deriveLegacySessionChatType } from "./session-contract.js";
const DISCORD_CHANNEL = "discord" as const;
@@ -160,9 +159,6 @@ export function createDiscordPluginBase(params: {
},
}),
},
messaging: {
deriveLegacySessionChatType,
},
security: discordSecurityAdapter,
secrets: {
secretTargetRegistryEntries,

View File

@@ -1,5 +1,5 @@
import { whatsappCommandPolicy as whatsappCommandPolicyImpl } from "./src/command-policy.js";
import { resolveLegacyGroupSessionKey as resolveLegacyGroupSessionKeyImpl } from "./src/group-session-contract.js";
import { resolveGroupSessionKey as resolveGroupSessionKeyImpl } from "./src/group-session-contract.js";
import { __testing as whatsappAccessControlTestingImpl } from "./src/inbound/access-control.js";
import {
isWhatsAppGroupJid as isWhatsAppGroupJidImpl,
@@ -10,20 +10,14 @@ export {
listWhatsAppDirectoryPeersFromConfig,
} from "./src/directory-config.js";
import { resolveWhatsAppRuntimeGroupPolicy as resolveWhatsAppRuntimeGroupPolicyImpl } from "./src/runtime-group-policy.js";
import {
canonicalizeLegacySessionKey as canonicalizeLegacySessionKeyImpl,
isLegacyGroupSessionKey as isLegacyGroupSessionKeyImpl,
} from "./src/session-contract.js";
export {
collectUnsupportedSecretRefConfigCandidates,
unsupportedSecretRefSurfacePatterns,
} from "./src/security-contract.js";
export const canonicalizeLegacySessionKey = canonicalizeLegacySessionKeyImpl;
export const isLegacyGroupSessionKey = isLegacyGroupSessionKeyImpl;
export const isWhatsAppGroupJid = isWhatsAppGroupJidImpl;
export const normalizeWhatsAppTarget = normalizeWhatsAppTargetImpl;
export const resolveLegacyGroupSessionKey = resolveLegacyGroupSessionKeyImpl;
export const resolveGroupSessionKey = resolveGroupSessionKeyImpl;
export const resolveWhatsAppRuntimeGroupPolicy = resolveWhatsAppRuntimeGroupPolicyImpl;
export const whatsappAccessControlTesting = whatsappAccessControlTestingImpl;
export const whatsappCommandPolicy = whatsappCommandPolicyImpl;

View File

@@ -1,6 +1,6 @@
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/string-coerce-runtime";
export function resolveLegacyGroupSessionKey(ctx: { From?: string }): {
export function resolveGroupSessionKey(ctx: { From?: string }): {
key: string;
channel: string;
id: string;

View File

@@ -1,9 +1,5 @@
import { describe, expect, it } from "vitest";
import {
canonicalizeLegacySessionKey,
deriveLegacySessionChatType,
isLegacyGroupSessionKey,
} from "./session-contract.js";
import { canonicalizeLegacySessionKey, isLegacyGroupSessionKey } from "./session-contract.js";
describe("whatsapp legacy session contract", () => {
it("canonicalizes legacy WhatsApp group keys to channel-qualified agent keys", () => {
@@ -20,12 +16,11 @@ describe("whatsapp legacy session contract", () => {
it("does not claim generic non-WhatsApp group keys", () => {
expect(isLegacyGroupSessionKey("group:abc")).toBe(false);
expect(deriveLegacySessionChatType("group:abc")).toBeUndefined();
expect(canonicalizeLegacySessionKey({ key: "group:abc", agentId: "main" })).toBeNull();
});
it("derives chat type for legacy WhatsApp group keys", () => {
expect(deriveLegacySessionChatType("123@g.us")).toBe("group");
expect(deriveLegacySessionChatType("whatsapp:123@g.us")).toBe("group");
it("identifies legacy WhatsApp group keys for doctor migration", () => {
expect(isLegacyGroupSessionKey("123@g.us")).toBe(true);
expect(isLegacyGroupSessionKey("whatsapp:123@g.us")).toBe(true);
});
});

View File

@@ -28,10 +28,6 @@ export function isLegacyGroupSessionKey(key: string): boolean {
return extractLegacyWhatsAppGroupId(key) !== null;
}
export function deriveLegacySessionChatType(key: string): "group" | undefined {
return isLegacyGroupSessionKey(key) ? "group" : undefined;
}
export function canonicalizeLegacySessionKey(params: {
key: string;
agentId: string;

View File

@@ -26,17 +26,12 @@ import {
import { formatWhatsAppConfigAllowFromEntries } from "./config-accessors.js";
import { WhatsAppChannelConfigSchema } from "./config-schema.js";
import { whatsappDoctor } from "./doctor.js";
import { resolveLegacyGroupSessionKey } from "./group-session-contract.js";
import { resolveGroupSessionKey } from "./group-session-contract.js";
import {
collectUnsupportedSecretRefConfigCandidates,
unsupportedSecretRefSurfacePatterns,
} from "./security-contract.js";
import { applyWhatsAppSecurityConfigFixes } from "./security-fix.js";
import {
canonicalizeLegacySessionKey,
deriveLegacySessionChatType,
isLegacyGroupSessionKey,
} from "./session-contract.js";
const WHATSAPP_CHANNEL = "whatsapp" as const;
@@ -259,11 +254,7 @@ export function createWhatsAppPluginBase(params: {
config: base.config!,
messaging: {
defaultMarkdownTableMode: "bullets",
deriveLegacySessionChatType,
resolveLegacyGroupSessionKey,
isLegacyGroupSessionKey,
canonicalizeLegacySessionKey: (params) =>
canonicalizeLegacySessionKey({ key: params.key, agentId: params.agentId }),
resolveGroupSessionKey,
},
secrets: {
unsupportedSecretRefSurfacePatterns,

View File

@@ -729,11 +729,7 @@ export function listBundledChannelLegacySessionSurfaces(
if (surface) {
return [surface];
}
if (!hasSetupEntryFeature(setupEntry, "legacySessionSurfaces")) {
return [];
}
const plugin = getBundledChannelSetupPluginForRoot(id, rootScope, loadContext);
return plugin?.messaging ? [plugin.messaging] : [];
return [];
});
}

View File

@@ -515,13 +515,7 @@ export type ChannelMessagingAdapter = {
sessionKey: string;
ctx: MsgContext;
}) => string | undefined;
deriveLegacySessionChatType?: (sessionKey: string) => "direct" | "group" | "channel" | undefined;
isLegacyGroupSessionKey?: (key: string) => boolean;
canonicalizeLegacySessionKey?: (params: {
key: string;
agentId: string;
}) => string | null | undefined;
resolveLegacyGroupSessionKey?: (ctx: MsgContext) => {
resolveGroupSessionKey?: (ctx: MsgContext) => {
key: string;
channel: string;
id: string;

View File

@@ -11,15 +11,15 @@ import type { GroupKeyResolution } from "./types.js";
const getGroupSurfaces = () => new Set<string>([...listDeliverableMessageChannels(), "webchat"]);
type LegacyGroupSessionSurface = {
resolveLegacyGroupSessionKey?: (ctx: MsgContext) => GroupKeyResolution | null;
type GroupSessionSurface = {
resolveGroupSessionKey?: (ctx: MsgContext) => GroupKeyResolution | null;
};
function resolveLegacyGroupSessionKey(ctx: MsgContext): GroupKeyResolution | null {
function resolvePluginGroupSessionKey(ctx: MsgContext): GroupKeyResolution | null {
for (const plugin of listChannelPlugins()) {
const resolved = (
plugin.messaging as LegacyGroupSessionSurface | undefined
)?.resolveLegacyGroupSessionKey?.(ctx);
plugin.messaging as GroupSessionSurface | undefined
)?.resolveGroupSessionKey?.(ctx);
if (resolved) {
return resolved;
}
@@ -107,13 +107,13 @@ export function resolveGroupSessionKey(ctx: MsgContext): GroupKeyResolution | nu
const normalizedChatType =
chatType === "channel" ? "channel" : chatType === "group" ? "group" : undefined;
const legacyResolution = resolveLegacyGroupSessionKey(ctx);
const pluginResolution = resolvePluginGroupSessionKey(ctx);
const looksLikeGroup =
normalizedChatType === "group" ||
normalizedChatType === "channel" ||
from.includes(":group:") ||
from.includes(":channel:") ||
legacyResolution !== null;
pluginResolution !== null;
if (!looksLikeGroup) {
return null;
}
@@ -124,11 +124,11 @@ export function resolveGroupSessionKey(ctx: MsgContext): GroupKeyResolution | nu
const head = normalizeLowercaseStringOrEmpty(parts[0]);
const headIsSurface = head ? getGroupSurfaces().has(head) : false;
if (!headIsSurface && !providerHint && legacyResolution) {
return legacyResolution;
if (!headIsSurface && !providerHint && pluginResolution) {
return pluginResolution;
}
const provider = headIsSurface ? head : (providerHint ?? legacyResolution?.channel);
const provider = headIsSurface ? head : (providerHint ?? pluginResolution?.channel);
if (!provider) {
return null;
}

View File

@@ -3,21 +3,7 @@ import { parseAgentSessionKey } from "./session-key-utils.js";
export type SessionKeyChatType = "direct" | "group" | "channel" | "unknown";
function deriveBuiltInLegacySessionChatType(
scopedSessionKey: string,
): SessionKeyChatType | undefined {
if (/^group:[^:]+$/.test(scopedSessionKey)) {
return "group";
}
return undefined;
}
export function deriveSessionChatTypeFromScopedKey(
scopedSessionKey: string,
deriveLegacySessionChatTypes: Array<
(scopedSessionKey: string) => SessionKeyChatType | undefined
> = [],
): SessionKeyChatType {
export function deriveSessionChatTypeFromScopedKey(scopedSessionKey: string): SessionKeyChatType {
const tokens = new Set(scopedSessionKey.split(":").filter(Boolean));
if (tokens.has("group")) {
return "group";
@@ -28,32 +14,17 @@ export function deriveSessionChatTypeFromScopedKey(
if (tokens.has("direct") || tokens.has("dm")) {
return "direct";
}
const builtInLegacy = deriveBuiltInLegacySessionChatType(scopedSessionKey);
if (builtInLegacy) {
return builtInLegacy;
}
for (const deriveLegacySessionChatType of deriveLegacySessionChatTypes) {
const derived = deriveLegacySessionChatType(scopedSessionKey);
if (derived) {
return derived;
}
}
return "unknown";
}
/**
* Best-effort chat-type extraction from session keys across canonical and legacy formats.
*/
/** Best-effort chat-type extraction from canonical session keys. */
export function deriveSessionChatTypeFromKey(
sessionKey: string | undefined | null,
deriveLegacySessionChatTypes: Array<
(scopedSessionKey: string) => SessionKeyChatType | undefined
> = [],
): SessionKeyChatType {
const raw = normalizeLowercaseStringOrEmpty(sessionKey);
if (!raw) {
return "unknown";
}
const scoped = parseAgentSessionKey(raw)?.rest ?? raw;
return deriveSessionChatTypeFromScopedKey(scoped, deriveLegacySessionChatTypes);
return deriveSessionChatTypeFromScopedKey(scoped);
}

View File

@@ -1,65 +1,13 @@
import { getBootstrapChannelPlugin } from "../channels/plugins/bootstrap-registry.js";
import { normalizeLowercaseStringOrEmpty } from "../shared/string-coerce.js";
import {
deriveSessionChatTypeFromKey,
type SessionKeyChatType,
} from "./session-chat-type-shared.js";
import { parseAgentSessionKey } from "./session-key-utils.js";
export {
deriveSessionChatTypeFromKey,
type SessionKeyChatType,
} from "./session-chat-type-shared.js";
type LegacySessionChatTypeDeriver = NonNullable<
NonNullable<ReturnType<typeof getBootstrapChannelPlugin>>["messaging"]
>["deriveLegacySessionChatType"];
function resolveScopedSessionKey(sessionKey: string | undefined | null): string {
const raw = normalizeLowercaseStringOrEmpty(sessionKey);
if (!raw) {
return "";
}
return parseAgentSessionKey(raw)?.rest ?? raw;
}
function collectLegacyChatTypeCandidatePluginIds(scopedSessionKey: string): string[] {
const ids = new Set<string>();
const firstToken = scopedSessionKey.split(":").find(Boolean);
if (firstToken) {
ids.add(firstToken);
}
if (scopedSessionKey.includes("@g.us")) {
ids.add("whatsapp");
}
return Array.from(ids);
}
function derivePluginLegacySessionChatType(
scopedSessionKey: string,
deriveLegacySessionChatType: LegacySessionChatTypeDeriver,
): SessionKeyChatType | undefined {
if (!deriveLegacySessionChatType) {
return undefined;
}
return deriveLegacySessionChatType(scopedSessionKey);
}
export function deriveSessionChatType(sessionKey: string | undefined | null): SessionKeyChatType {
const builtInType = deriveSessionChatTypeFromKey(sessionKey);
if (builtInType !== "unknown") {
return builtInType;
}
const scopedSessionKey = resolveScopedSessionKey(sessionKey);
for (const pluginId of collectLegacyChatTypeCandidatePluginIds(scopedSessionKey)) {
const derived = derivePluginLegacySessionChatType(
scopedSessionKey,
getBootstrapChannelPlugin(pluginId)?.messaging?.deriveLegacySessionChatType,
);
if (derived) {
return derived;
}
}
return "unknown";
return deriveSessionChatTypeFromKey(sessionKey);
}