perf: skip bundled session fallback on hot paths

This commit is contained in:
Peter Steinberger
2026-04-11 01:17:42 +01:00
parent 7392060c3f
commit b146c0c26b
11 changed files with 112 additions and 15 deletions

View File

@@ -2,7 +2,7 @@ import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { describe, expect, it } from "vitest";
import { clearSessionStoreCacheForTest } from "../../../src/config/sessions.js";
import { clearSessionStoreCacheForTest } from "../../../src/config/sessions/store.js";
import {
createDiscordNativeApprovalAdapter,
getDiscordApprovalCapability,

View File

@@ -90,6 +90,7 @@ function createDiscordOriginTargetResolver(configOverride?: DiscordExecApprovalC
const sessionConversation = resolveApprovalRequestSessionConversation({
request,
channel: "discord",
bundledFallback: false,
});
const sessionKind = extractDiscordSessionKind(
normalizeOptionalString(request.request.sessionKey) ?? null,
@@ -113,6 +114,7 @@ function createDiscordOriginTargetResolver(configOverride?: DiscordExecApprovalC
const sessionConversation = resolveApprovalRequestSessionConversation({
request,
channel: "discord",
bundledFallback: false,
});
const sessionKind = extractDiscordSessionKind(request.request.sessionKey?.trim() || null);
if (sessionKind === "dm") {
@@ -134,6 +136,7 @@ function createDiscordOriginTargetResolver(configOverride?: DiscordExecApprovalC
const sessionConversation = resolveApprovalRequestSessionConversation({
request,
channel: "discord",
bundledFallback: false,
});
const sessionKind = extractDiscordSessionKind(request.request.sessionKey?.trim() || null);
if (sessionKind === "dm") {

View File

@@ -3,7 +3,7 @@ import os from "node:os";
import path from "node:path";
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
import { describe, expect, it } from "vitest";
import { clearSessionStoreCacheForTest } from "../../../src/config/sessions.js";
import { clearSessionStoreCacheForTest } from "../../../src/config/sessions/store.js";
import { slackApprovalCapability, slackNativeApprovalAdapter } from "./approval-native.js";
function buildConfig(

View File

@@ -96,6 +96,7 @@ function resolveSlackFallbackOriginTarget(request: ApprovalRequest): SlackOrigin
const sessionTarget = resolveApprovalRequestSessionConversation({
request,
channel: "slack",
bundledFallback: false,
});
if (!sessionTarget) {
return null;

View File

@@ -3,7 +3,7 @@ import os from "node:os";
import path from "node:path";
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
import { describe, expect, it } from "vitest";
import { clearSessionStoreCacheForTest } from "../../../src/config/sessions.js";
import { clearSessionStoreCacheForTest } from "../../../src/config/sessions/store.js";
import { telegramApprovalCapability, telegramNativeApprovalAdapter } from "./approval-native.js";
function buildConfig(

View File

@@ -125,7 +125,7 @@ function buildGenericParentOverrideCandidates(sessionKey: string | null | undefi
return [];
}
const { baseSessionKey, threadId } = parseThreadSessionSuffix(raw.rawId);
return buildChannelKeyCandidates(threadId ? baseSessionKey : undefined);
return buildChannelKeyCandidates(threadId ? baseSessionKey : raw.rawId);
}
function buildFeishuParentOverrideCandidates(rawId: string | undefined): string[] {

View File

@@ -1,5 +1,5 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { clearRuntimeConfigSnapshot, setRuntimeConfigSnapshot } from "../../config/config.js";
import { clearRuntimeConfigSnapshot, setRuntimeConfigSnapshot } from "../../config/io.js";
import { resetPluginRuntimeStateForTest } from "../../plugins/runtime.js";
const fallbackState = vi.hoisted(() => ({
@@ -27,7 +27,7 @@ vi.mock("../../plugin-sdk/facade-runtime.js", async () => {
};
});
import { resolveSessionConversationRef } from "./session-conversation.js";
import { resolveSessionConversationRef, resolveSessionThreadInfo } from "./session-conversation.js";
describe("session conversation bundled fallback", () => {
beforeEach(() => {
@@ -73,6 +73,51 @@ describe("session conversation bundled fallback", () => {
});
});
it("can skip bundled fallback probing for hot generic-only callers", () => {
fallbackState.activeDirName = "mock-threaded";
fallbackState.resolveSessionConversation = ({ rawId }) => {
const [conversationId, threadId] = rawId.split(":topic:");
return {
id: conversationId,
threadId,
baseConversationId: conversationId,
parentConversationCandidates: [conversationId],
};
};
setRuntimeConfigSnapshot({
plugins: {
entries: {
"mock-threaded": {
enabled: true,
},
},
},
});
expect(
resolveSessionConversationRef("agent:main:mock-threaded:group:room:topic:42", {
bundledFallback: false,
}),
).toEqual({
channel: "mock-threaded",
kind: "group",
rawId: "room:topic:42",
id: "room:topic:42",
threadId: undefined,
baseSessionKey: "agent:main:mock-threaded:group:room:topic:42",
baseConversationId: "room:topic:42",
parentConversationCandidates: [],
});
expect(
resolveSessionThreadInfo("agent:main:mock-threaded:group:room:topic:42", {
bundledFallback: false,
}),
).toEqual({
baseSessionKey: "agent:main:mock-threaded:group:room:topic:42",
threadId: undefined,
});
});
it("uses explicit bundled parent candidates before registry bootstrap", () => {
fallbackState.activeDirName = "mock-parent";
fallbackState.resolveSessionConversation = ({ rawId }) => ({

View File

@@ -1,5 +1,5 @@
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import { clearRuntimeConfigSnapshot } from "../../config/config.js";
import { clearRuntimeConfigSnapshot, setRuntimeConfigSnapshot } from "../../config/io.js";
import { resetPluginRuntimeStateForTest, setActivePluginRegistry } from "../../plugins/runtime.js";
import { createTestRegistry } from "../../test-utils/channel-plugins.js";
import { createSessionConversationTestRegistry } from "../../test-utils/session-conversation-registry.js";
@@ -56,6 +56,15 @@ describe("session conversation routing", () => {
it("does not load bundled session-key fallbacks for inactive channel plugins", () => {
resetPluginRuntimeStateForTest();
setRuntimeConfigSnapshot({
plugins: {
entries: {
telegram: {
enabled: false,
},
},
},
});
expect(resolveSessionConversationRef("agent:main:telegram:group:-100123:topic:77")).toEqual({
channel: "telegram",

View File

@@ -1,3 +1,4 @@
import { getRuntimeConfigSnapshot } from "../../config/runtime-snapshot.js";
import { tryLoadActivatedBundledPluginPublicSurfaceModuleSync } from "../../plugin-sdk/facade-runtime.js";
import {
parseRawSessionConversationRef,
@@ -49,6 +50,9 @@ type BundledSessionKeyModule = {
};
const SESSION_KEY_API_ARTIFACT_BASENAME = "session-key-api.js";
type SessionConversationResolutionOptions = {
bundledFallback?: boolean;
};
type NormalizedSessionConversationResolution = ResolvedSessionConversation & {
hasExplicitParentConversationCandidates: boolean;
@@ -140,6 +144,9 @@ function resolveBundledSessionConversationFallback(params: {
kind: "group" | "channel";
rawId: string;
}): NormalizedSessionConversationResolution | null {
if (isBundledSessionConversationFallbackDisabled(params.channel)) {
return null;
}
const dirName = normalizeResolvedChannel(params.channel);
let resolveSessionConversation: BundledSessionKeyModule["resolveSessionConversation"];
try {
@@ -163,10 +170,23 @@ function resolveBundledSessionConversationFallback(params: {
);
}
function isBundledSessionConversationFallbackDisabled(channel: string): boolean {
const snapshot = getRuntimeConfigSnapshot();
if (!snapshot?.plugins) {
return false;
}
if (snapshot.plugins.enabled === false) {
return true;
}
const entry = snapshot.plugins.entries?.[normalizeResolvedChannel(channel)];
return !!entry && typeof entry === "object" && entry.enabled === false;
}
function resolveSessionConversationResolution(params: {
channel: string;
kind: "group" | "channel";
rawId: string;
bundledFallback?: boolean;
}): ResolvedSessionConversation | null {
const rawId = params.rawId.trim();
if (!rawId) {
@@ -180,13 +200,16 @@ function resolveSessionConversationResolution(params: {
rawId,
}),
);
const shouldTryBundledFallback = params.bundledFallback !== false && !messaging;
const resolved =
pluginResolved ??
resolveBundledSessionConversationFallback({
channel: params.channel,
kind: params.kind,
rawId,
}) ??
(shouldTryBundledFallback
? resolveBundledSessionConversationFallback({
channel: params.channel,
kind: params.kind,
rawId,
})
: null) ??
buildGenericConversationResolution(rawId);
if (!resolved) {
return null;
@@ -214,6 +237,7 @@ export function resolveSessionConversation(params: {
channel: string;
kind: "group" | "channel";
rawId: string;
bundledFallback?: boolean;
}): ResolvedSessionConversation | null {
return resolveSessionConversationResolution(params);
}
@@ -224,13 +248,17 @@ function buildBaseSessionKey(raw: RawSessionConversationRef, id: string): string
export function resolveSessionConversationRef(
sessionKey: string | undefined | null,
opts: SessionConversationResolutionOptions = {},
): ResolvedSessionConversationRef | null {
const raw = parseRawSessionConversationRef(sessionKey);
if (!raw) {
return null;
}
const resolved = resolveSessionConversation(raw);
const resolved = resolveSessionConversation({
...raw,
bundledFallback: opts.bundledFallback,
});
if (!resolved) {
return null;
}
@@ -249,8 +277,9 @@ export function resolveSessionConversationRef(
export function resolveSessionThreadInfo(
sessionKey: string | undefined | null,
opts: SessionConversationResolutionOptions = {},
): ParsedThreadSessionSuffix {
const resolved = resolveSessionConversationRef(sessionKey);
const resolved = resolveSessionConversationRef(sessionKey, opts);
if (!resolved) {
return parseThreadSessionSuffix(sessionKey);
}

View File

@@ -10,3 +10,10 @@ export function parseSessionThreadInfo(sessionKey: string | undefined): {
} {
return resolveSessionThreadInfo(sessionKey);
}
export function parseSessionThreadInfoFast(sessionKey: string | undefined): {
baseSessionKey: string | undefined;
threadId: string | undefined;
} {
return resolveSessionThreadInfo(sessionKey, { bundledFallback: false });
}

View File

@@ -87,12 +87,15 @@ function normalizeOptionalChannel(value?: string | null): string | undefined {
export function resolveApprovalRequestSessionConversation(params: {
request: ApprovalRequestLike;
channel?: string | null;
bundledFallback?: boolean;
}): ApprovalRequestSessionConversation | null {
const sessionKey = normalizeOptionalString(params.request.request.sessionKey);
if (!sessionKey) {
return null;
}
const resolved = resolveSessionConversationRef(sessionKey);
const resolved = resolveSessionConversationRef(sessionKey, {
bundledFallback: params.bundledFallback,
});
if (!resolved) {
return null;
}