mirror of https://github.com/openclaw/openclaw.git
fix: tighten plugin session conversation routing
This commit is contained in:
parent
42a74bb635
commit
c6b3d134f9
|
|
@ -154,7 +154,7 @@
|
|||
"exportName": "ChannelMessageActionAdapter",
|
||||
"kind": "type",
|
||||
"source": {
|
||||
"line": 529,
|
||||
"line": 551,
|
||||
"path": "src/channels/plugins/types.core.ts"
|
||||
}
|
||||
},
|
||||
|
|
@ -163,7 +163,7 @@
|
|||
"exportName": "ChannelMessageActionContext",
|
||||
"kind": "type",
|
||||
"source": {
|
||||
"line": 493,
|
||||
"line": 515,
|
||||
"path": "src/channels/plugins/types.core.ts"
|
||||
}
|
||||
},
|
||||
|
|
@ -1170,7 +1170,7 @@
|
|||
"exportName": "BaseProbeResult",
|
||||
"kind": "type",
|
||||
"source": {
|
||||
"line": 572,
|
||||
"line": 594,
|
||||
"path": "src/channels/plugins/types.core.ts"
|
||||
}
|
||||
},
|
||||
|
|
@ -1179,7 +1179,7 @@
|
|||
"exportName": "BaseTokenResolution",
|
||||
"kind": "type",
|
||||
"source": {
|
||||
"line": 578,
|
||||
"line": 600,
|
||||
"path": "src/channels/plugins/types.core.ts"
|
||||
}
|
||||
},
|
||||
|
|
@ -1224,7 +1224,7 @@
|
|||
"exportName": "ChannelMessageActionAdapter",
|
||||
"kind": "type",
|
||||
"source": {
|
||||
"line": 529,
|
||||
"line": 551,
|
||||
"path": "src/channels/plugins/types.core.ts"
|
||||
}
|
||||
},
|
||||
|
|
@ -1233,7 +1233,7 @@
|
|||
"exportName": "ChannelMessageActionContext",
|
||||
"kind": "type",
|
||||
"source": {
|
||||
"line": 493,
|
||||
"line": 515,
|
||||
"path": "src/channels/plugins/types.core.ts"
|
||||
}
|
||||
},
|
||||
|
|
@ -1752,7 +1752,7 @@
|
|||
"exportName": "BaseProbeResult",
|
||||
"kind": "type",
|
||||
"source": {
|
||||
"line": 572,
|
||||
"line": 594,
|
||||
"path": "src/channels/plugins/types.core.ts"
|
||||
}
|
||||
},
|
||||
|
|
@ -1761,7 +1761,7 @@
|
|||
"exportName": "BaseTokenResolution",
|
||||
"kind": "type",
|
||||
"source": {
|
||||
"line": 578,
|
||||
"line": 600,
|
||||
"path": "src/channels/plugins/types.core.ts"
|
||||
}
|
||||
},
|
||||
|
|
@ -1797,7 +1797,7 @@
|
|||
"exportName": "ChannelAgentPromptAdapter",
|
||||
"kind": "type",
|
||||
"source": {
|
||||
"line": 466,
|
||||
"line": 488,
|
||||
"path": "src/channels/plugins/types.core.ts"
|
||||
}
|
||||
},
|
||||
|
|
@ -1977,7 +1977,7 @@
|
|||
"exportName": "ChannelDirectoryEntry",
|
||||
"kind": "type",
|
||||
"source": {
|
||||
"line": 480,
|
||||
"line": 502,
|
||||
"path": "src/channels/plugins/types.core.ts"
|
||||
}
|
||||
},
|
||||
|
|
@ -1986,7 +1986,7 @@
|
|||
"exportName": "ChannelDirectoryEntryKind",
|
||||
"kind": "type",
|
||||
"source": {
|
||||
"line": 478,
|
||||
"line": 500,
|
||||
"path": "src/channels/plugins/types.core.ts"
|
||||
}
|
||||
},
|
||||
|
|
@ -2130,7 +2130,7 @@
|
|||
"exportName": "ChannelMessageActionAdapter",
|
||||
"kind": "type",
|
||||
"source": {
|
||||
"line": 529,
|
||||
"line": 551,
|
||||
"path": "src/channels/plugins/types.core.ts"
|
||||
}
|
||||
},
|
||||
|
|
@ -2139,7 +2139,7 @@
|
|||
"exportName": "ChannelMessageActionContext",
|
||||
"kind": "type",
|
||||
"source": {
|
||||
"line": 493,
|
||||
"line": 515,
|
||||
"path": "src/channels/plugins/types.core.ts"
|
||||
}
|
||||
},
|
||||
|
|
@ -2274,7 +2274,7 @@
|
|||
"exportName": "ChannelPollContext",
|
||||
"kind": "type",
|
||||
"source": {
|
||||
"line": 560,
|
||||
"line": 582,
|
||||
"path": "src/channels/plugins/types.core.ts"
|
||||
}
|
||||
},
|
||||
|
|
@ -2283,7 +2283,7 @@
|
|||
"exportName": "ChannelPollResult",
|
||||
"kind": "type",
|
||||
"source": {
|
||||
"line": 551,
|
||||
"line": 573,
|
||||
"path": "src/channels/plugins/types.core.ts"
|
||||
}
|
||||
},
|
||||
|
|
@ -2427,7 +2427,7 @@
|
|||
"exportName": "ChannelToolSend",
|
||||
"kind": "type",
|
||||
"source": {
|
||||
"line": 522,
|
||||
"line": 544,
|
||||
"path": "src/channels/plugins/types.core.ts"
|
||||
}
|
||||
},
|
||||
|
|
@ -2683,7 +2683,7 @@
|
|||
"exportName": "buildCommandsMessage",
|
||||
"kind": "function",
|
||||
"source": {
|
||||
"line": 1075,
|
||||
"line": 1076,
|
||||
"path": "src/auto-reply/status.ts"
|
||||
}
|
||||
},
|
||||
|
|
@ -2692,7 +2692,7 @@
|
|||
"exportName": "buildCommandsMessagePaginated",
|
||||
"kind": "function",
|
||||
"source": {
|
||||
"line": 1084,
|
||||
"line": 1085,
|
||||
"path": "src/auto-reply/status.ts"
|
||||
}
|
||||
},
|
||||
|
|
@ -2728,7 +2728,7 @@
|
|||
"exportName": "buildHelpMessage",
|
||||
"kind": "function",
|
||||
"source": {
|
||||
"line": 870,
|
||||
"line": 871,
|
||||
"path": "src/auto-reply/status.ts"
|
||||
}
|
||||
},
|
||||
|
|
@ -3738,7 +3738,7 @@
|
|||
"exportName": "ChannelMessageActionContext",
|
||||
"kind": "type",
|
||||
"source": {
|
||||
"line": 493,
|
||||
"line": 515,
|
||||
"path": "src/channels/plugins/types.core.ts"
|
||||
}
|
||||
},
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -36,10 +36,16 @@ Core owns the shared message tool, prompt wiring, the outer session-key shape,
|
|||
generic `:thread:` bookkeeping, and dispatch.
|
||||
|
||||
If your platform stores extra scope inside conversation ids, keep that parsing
|
||||
in the plugin by implementing `messaging.resolveSessionConversation(...)` and
|
||||
`messaging.resolveParentConversationCandidates(...)`. Use those hooks for
|
||||
platform-specific suffixes or inheritance rules instead of adding provider
|
||||
checks to core.
|
||||
in the plugin with `messaging.resolveSessionConversation(...)`. That is the
|
||||
canonical hook for mapping `rawId` to the base conversation id, optional thread
|
||||
id, and any `parentConversationCandidates`.
|
||||
|
||||
`messaging.resolveParentConversationCandidates(...)` remains available as a
|
||||
legacy compatibility fallback when a plugin only needs parent fallbacks on top
|
||||
of the generic/raw id. If both hooks exist, core uses
|
||||
`resolveSessionConversation(...).parentConversationCandidates` first and only
|
||||
falls back to `resolveParentConversationCandidates(...)` when the canonical hook
|
||||
omits them.
|
||||
|
||||
## Approvals and channel capabilities
|
||||
|
||||
|
|
|
|||
|
|
@ -501,6 +501,7 @@ function resolveDiscordThreadTitleModelRef(params: {
|
|||
cfg: params.cfg,
|
||||
channel,
|
||||
groupId: params.threadId,
|
||||
groupChatType: "channel",
|
||||
groupChannel,
|
||||
groupSubject: groupChannel,
|
||||
parentSessionKey,
|
||||
|
|
|
|||
|
|
@ -200,17 +200,14 @@ describe("feishuPlugin messaging", () => {
|
|||
parentConversationCandidates: ["oc_group_chat:topic:om_topic_root", "oc_group_chat"],
|
||||
});
|
||||
expect(
|
||||
feishuPlugin.messaging?.resolveParentConversationCandidates?.({
|
||||
kind: "group",
|
||||
rawId: "oc_group_chat:topic:om_topic_root:sender:ou_topic_user",
|
||||
}),
|
||||
).toEqual(["oc_group_chat:topic:om_topic_root", "oc_group_chat"]);
|
||||
expect(
|
||||
feishuPlugin.messaging?.resolveParentConversationCandidates?.({
|
||||
feishuPlugin.messaging?.resolveSessionConversation?.({
|
||||
kind: "group",
|
||||
rawId: "oc_group_chat:topic:om_topic_root",
|
||||
}),
|
||||
).toEqual(["oc_group_chat"]);
|
||||
).toEqual({
|
||||
id: "oc_group_chat:topic:om_topic_root",
|
||||
parentConversationCandidates: ["oc_group_chat"],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1106,8 +1106,6 @@ export const feishuPlugin: ChannelPlugin<ResolvedFeishuAccount, FeishuProbeResul
|
|||
messaging: {
|
||||
normalizeTarget: (raw) => normalizeFeishuTarget(raw) ?? undefined,
|
||||
resolveSessionConversation: ({ rawId }) => resolveFeishuSessionConversation(rawId),
|
||||
resolveParentConversationCandidates: ({ rawId }) =>
|
||||
resolveFeishuParentConversationCandidates(rawId),
|
||||
resolveOutboundSessionRoute: (params) => resolveFeishuOutboundSessionRoute(params),
|
||||
targetResolver: {
|
||||
looksLikeId: looksLikeFeishuId,
|
||||
|
|
|
|||
|
|
@ -200,11 +200,15 @@ describe("telegramPlugin messaging", () => {
|
|||
parentConversationCandidates: ["-1001"],
|
||||
});
|
||||
expect(
|
||||
telegramPlugin.messaging?.resolveParentConversationCandidates?.({
|
||||
telegramPlugin.messaging?.resolveSessionConversation?.({
|
||||
kind: "group",
|
||||
rawId: "-1001:topic:77",
|
||||
rawId: "-1001:Topic:77",
|
||||
}),
|
||||
).toEqual(["-1001"]);
|
||||
).toEqual({
|
||||
id: "-1001",
|
||||
threadId: "77",
|
||||
parentConversationCandidates: ["-1001"],
|
||||
});
|
||||
expect(
|
||||
telegramPlugin.messaging?.resolveSessionConversation?.({
|
||||
kind: "group",
|
||||
|
|
|
|||
|
|
@ -543,8 +543,6 @@ export const telegramPlugin = createChatChannelPlugin({
|
|||
messaging: {
|
||||
normalizeTarget: normalizeTelegramMessagingTarget,
|
||||
resolveSessionConversation: ({ rawId }) => resolveTelegramSessionConversation(rawId),
|
||||
resolveParentConversationCandidates: ({ rawId }) =>
|
||||
resolveTelegramSessionConversation(rawId)?.parentConversationCandidates ?? null,
|
||||
parseExplicitTarget: ({ raw }) => parseTelegramExplicitTarget(raw),
|
||||
inferTargetChatType: ({ to }) => parseTelegramExplicitTarget(to).chatType,
|
||||
formatTargetDisplay: ({ target, display, kind }) => {
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ export function parseTelegramTopicConversation(params: {
|
|||
parentConversationId?: string;
|
||||
}): ParsedTelegramTopicConversation | null {
|
||||
const conversation = params.conversationId.trim();
|
||||
const directMatch = conversation.match(/^(-?\d+):topic:(\d+)$/);
|
||||
const directMatch = conversation.match(/^(-?\d+):topic:(\d+)$/i);
|
||||
if (directMatch?.[1] && directMatch[2]) {
|
||||
const canonicalConversationId = buildTelegramTopicConversationId({
|
||||
chatId: directMatch[1],
|
||||
|
|
|
|||
|
|
@ -267,6 +267,7 @@ export async function getReplyFromConfig(
|
|||
: undefined) ??
|
||||
finalized.Provider,
|
||||
groupId: groupResolution?.id ?? sessionEntry.groupId,
|
||||
groupChatType: sessionEntry.chatType ?? sessionCtx.ChatType ?? finalized.ChatType,
|
||||
groupChannel: sessionEntry.groupChannel ?? sessionCtx.GroupChannel ?? finalized.GroupChannel,
|
||||
groupSubject: sessionEntry.subject ?? sessionCtx.GroupSubject ?? finalized.GroupSubject,
|
||||
parentSessionKey: sessionCtx.ParentSessionKey,
|
||||
|
|
|
|||
|
|
@ -763,6 +763,7 @@ export function buildStatusMessage(args: StatusArgs): string {
|
|||
cfg: args.config,
|
||||
channel: entry.channel ?? entry.origin?.provider,
|
||||
groupId: entry.groupId,
|
||||
groupChatType: entry.chatType ?? entry.origin?.chatType,
|
||||
groupChannel: entry.groupChannel,
|
||||
groupSubject: entry.subject,
|
||||
parentSessionKey: args.parentSessionKey,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { beforeEach, describe, expect, it } from "vitest";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import { setActivePluginRegistry } from "../plugins/runtime.js";
|
||||
import { createTestRegistry } from "../test-utils/channel-plugins.js";
|
||||
import { createSessionConversationTestRegistry } from "../test-utils/session-conversation-registry.js";
|
||||
import { resolveChannelModelOverride } from "./model-overrides.js";
|
||||
|
||||
|
|
@ -110,4 +111,60 @@ describe("resolveChannelModelOverride", () => {
|
|||
expect(resolved?.model).toBe(expected.model);
|
||||
expect(resolved?.matchKey).toBe(expected.matchKey);
|
||||
});
|
||||
|
||||
it("passes channel kind to plugin-owned parent fallback resolution", () => {
|
||||
setActivePluginRegistry(
|
||||
createTestRegistry([
|
||||
{
|
||||
pluginId: "channel-kind",
|
||||
source: "test",
|
||||
plugin: {
|
||||
id: "channel-kind",
|
||||
meta: {
|
||||
id: "channel-kind",
|
||||
label: "Channel Kind",
|
||||
selectionLabel: "Channel Kind",
|
||||
docsPath: "/channels/channel-kind",
|
||||
blurb: "test stub.",
|
||||
},
|
||||
capabilities: { chatTypes: ["group", "channel"] },
|
||||
messaging: {
|
||||
resolveSessionConversation: ({
|
||||
kind,
|
||||
rawId,
|
||||
}: {
|
||||
kind: "group" | "channel";
|
||||
rawId: string;
|
||||
}) => ({
|
||||
id: rawId,
|
||||
parentConversationCandidates: kind === "channel" ? ["thread-parent"] : [],
|
||||
}),
|
||||
},
|
||||
config: {
|
||||
listAccountIds: () => ["default"],
|
||||
resolveAccount: () => ({}),
|
||||
},
|
||||
},
|
||||
},
|
||||
]),
|
||||
);
|
||||
|
||||
const resolved = resolveChannelModelOverride({
|
||||
cfg: {
|
||||
channels: {
|
||||
modelByChannel: {
|
||||
"channel-kind": {
|
||||
"thread-parent": "demo-provider/demo-channel-model",
|
||||
},
|
||||
},
|
||||
},
|
||||
} as unknown as OpenClawConfig,
|
||||
channel: "channel-kind",
|
||||
groupId: "thread-123",
|
||||
groupChatType: "channel",
|
||||
});
|
||||
|
||||
expect(resolved?.model).toBe("demo-provider/demo-channel-model");
|
||||
expect(resolved?.matchKey).toBe("thread-parent");
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -6,8 +6,9 @@ import {
|
|||
resolveChannelEntryMatchWithFallback,
|
||||
type ChannelMatchSource,
|
||||
} from "./channel-config.js";
|
||||
import { normalizeChatType } from "./chat-type.js";
|
||||
import {
|
||||
resolveParentConversationCandidates,
|
||||
resolveSessionConversation,
|
||||
resolveSessionConversationRef,
|
||||
} from "./plugins/session-conversation.js";
|
||||
|
||||
|
|
@ -24,6 +25,7 @@ type ChannelModelOverrideParams = {
|
|||
cfg: OpenClawConfig;
|
||||
channel?: string | null;
|
||||
groupId?: string | null;
|
||||
groupChatType?: string | null;
|
||||
groupChannel?: string | null;
|
||||
groupSubject?: string | null;
|
||||
parentSessionKey?: string | null;
|
||||
|
|
@ -48,13 +50,24 @@ function resolveProviderEntry(
|
|||
function buildChannelCandidates(
|
||||
params: Pick<
|
||||
ChannelModelOverrideParams,
|
||||
"channel" | "groupId" | "groupChannel" | "groupSubject" | "parentSessionKey"
|
||||
"channel" | "groupId" | "groupChatType" | "groupChannel" | "groupSubject" | "parentSessionKey"
|
||||
>,
|
||||
): { keys: string[]; parentKeys: string[] } {
|
||||
const normalizedChannel =
|
||||
normalizeMessageChannel(params.channel ?? "") ?? params.channel?.trim().toLowerCase();
|
||||
const groupId = params.groupId?.trim();
|
||||
const sessionConversation = resolveSessionConversationRef(params.parentSessionKey);
|
||||
const groupConversationKind =
|
||||
normalizeChatType(params.groupChatType ?? undefined) === "channel"
|
||||
? "channel"
|
||||
: sessionConversation?.kind === "channel"
|
||||
? "channel"
|
||||
: "group";
|
||||
const groupConversation = resolveSessionConversation({
|
||||
channel: normalizedChannel ?? "",
|
||||
kind: groupConversationKind,
|
||||
rawId: groupId ?? "",
|
||||
});
|
||||
const groupChannel = params.groupChannel?.trim();
|
||||
const groupSubject = params.groupSubject?.trim();
|
||||
const channelBare = groupChannel ? groupChannel.replace(/^#/, "") : undefined;
|
||||
|
|
@ -74,11 +87,7 @@ function buildChannelCandidates(
|
|||
subjectSlug,
|
||||
),
|
||||
parentKeys: buildChannelKeyCandidates(
|
||||
...resolveParentConversationCandidates({
|
||||
channel: normalizedChannel ?? "",
|
||||
kind: "group",
|
||||
rawId: groupId ?? "",
|
||||
}),
|
||||
...(groupConversation?.parentConversationCandidates ?? []),
|
||||
...(sessionConversation?.parentConversationCandidates ?? []),
|
||||
),
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
import { beforeEach, describe, expect, it } from "vitest";
|
||||
import { setActivePluginRegistry } from "../../plugins/runtime.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";
|
||||
import {
|
||||
resolveParentConversationCandidates,
|
||||
resolveSessionConversation,
|
||||
resolveSessionConversationRef,
|
||||
resolveSessionParentSessionKey,
|
||||
resolveSessionThreadInfo,
|
||||
|
|
@ -46,6 +47,20 @@ describe("session conversation routing", () => {
|
|||
);
|
||||
});
|
||||
|
||||
it("keeps bundled Telegram topic parsing available before registry bootstrap", () => {
|
||||
resetPluginRuntimeStateForTest();
|
||||
|
||||
expect(resolveSessionConversationRef("agent:main:telegram:group:-100123:topic:77")).toEqual({
|
||||
channel: "telegram",
|
||||
kind: "group",
|
||||
rawId: "-100123:topic:77",
|
||||
id: "-100123",
|
||||
threadId: "77",
|
||||
baseSessionKey: "agent:main:telegram:group:-100123",
|
||||
parentConversationCandidates: ["-100123"],
|
||||
});
|
||||
});
|
||||
|
||||
it("lets Feishu own parent fallback candidates", () => {
|
||||
expect(
|
||||
resolveSessionConversationRef(
|
||||
|
|
@ -61,17 +76,52 @@ describe("session conversation routing", () => {
|
|||
"agent:main:feishu:group:oc_group_chat:topic:om_topic_root:sender:ou_topic_user",
|
||||
parentConversationCandidates: ["oc_group_chat:topic:om_topic_root", "oc_group_chat"],
|
||||
});
|
||||
expect(
|
||||
resolveParentConversationCandidates({
|
||||
channel: "feishu",
|
||||
kind: "group",
|
||||
rawId: "oc_group_chat:topic:om_topic_root:sender:ou_topic_user",
|
||||
}),
|
||||
).toEqual(["oc_group_chat:topic:om_topic_root", "oc_group_chat"]);
|
||||
expect(
|
||||
resolveSessionParentSessionKey(
|
||||
"agent:main:feishu:group:oc_group_chat:topic:om_topic_root:sender:ou_topic_user",
|
||||
),
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
it("keeps the legacy parent-candidate hook as a fallback only", () => {
|
||||
setActivePluginRegistry(
|
||||
createTestRegistry([
|
||||
{
|
||||
pluginId: "legacy-parent",
|
||||
source: "test",
|
||||
plugin: {
|
||||
id: "legacy-parent",
|
||||
meta: {
|
||||
id: "legacy-parent",
|
||||
label: "Legacy Parent",
|
||||
selectionLabel: "Legacy Parent",
|
||||
docsPath: "/channels/legacy-parent",
|
||||
blurb: "test stub.",
|
||||
},
|
||||
capabilities: { chatTypes: ["group"] },
|
||||
messaging: {
|
||||
resolveParentConversationCandidates: ({ rawId }: { rawId: string }) =>
|
||||
rawId.endsWith(":sender:user") ? [rawId.replace(/:sender:user$/i, "")] : null,
|
||||
},
|
||||
config: {
|
||||
listAccountIds: () => ["default"],
|
||||
resolveAccount: () => ({}),
|
||||
},
|
||||
},
|
||||
},
|
||||
]),
|
||||
);
|
||||
|
||||
expect(
|
||||
resolveSessionConversation({
|
||||
channel: "legacy-parent",
|
||||
kind: "group",
|
||||
rawId: "room:sender:user",
|
||||
}),
|
||||
).toEqual({
|
||||
id: "room:sender:user",
|
||||
threadId: undefined,
|
||||
parentConversationCandidates: ["room"],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { parseTelegramTopicConversation } from "../../acp/conversation-id.js";
|
||||
import {
|
||||
parseRawSessionConversationRef,
|
||||
parseThreadSessionSuffix,
|
||||
|
|
@ -23,7 +24,15 @@ export type ResolvedSessionConversationRef = {
|
|||
parentConversationCandidates: string[];
|
||||
};
|
||||
|
||||
type SessionConversationResolution = ResolvedSessionConversation;
|
||||
type SessionConversationHookResult = {
|
||||
id: string;
|
||||
threadId?: string | null;
|
||||
parentConversationCandidates?: string[];
|
||||
};
|
||||
|
||||
type NormalizedSessionConversationResolution = ResolvedSessionConversation & {
|
||||
hasExplicitParentConversationCandidates: boolean;
|
||||
};
|
||||
|
||||
function normalizeResolvedChannel(channel: string): string {
|
||||
return (
|
||||
|
|
@ -80,40 +89,82 @@ function buildGenericConversationResolution(rawId: string): ResolvedSessionConve
|
|||
};
|
||||
}
|
||||
|
||||
function normalizeSessionConversationResolution(
|
||||
resolved: SessionConversationHookResult | null | undefined,
|
||||
): NormalizedSessionConversationResolution | null {
|
||||
if (!resolved?.id?.trim()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
id: resolved.id.trim(),
|
||||
threadId: resolved.threadId?.trim() || undefined,
|
||||
parentConversationCandidates: dedupeConversationIds(
|
||||
resolved.parentConversationCandidates ?? [],
|
||||
),
|
||||
hasExplicitParentConversationCandidates: Object.hasOwn(
|
||||
resolved,
|
||||
"parentConversationCandidates",
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
function resolveBundledSessionConversationFallback(params: {
|
||||
channel: string;
|
||||
rawId: string;
|
||||
}): NormalizedSessionConversationResolution | null {
|
||||
if (normalizeResolvedChannel(params.channel) !== "telegram") {
|
||||
return null;
|
||||
}
|
||||
|
||||
const parsed = parseTelegramTopicConversation({ conversationId: params.rawId });
|
||||
if (!parsed) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
id: parsed.chatId,
|
||||
threadId: parsed.topicId,
|
||||
parentConversationCandidates: [parsed.chatId],
|
||||
hasExplicitParentConversationCandidates: true,
|
||||
};
|
||||
}
|
||||
|
||||
function resolveSessionConversationResolution(params: {
|
||||
channel: string;
|
||||
kind: "group" | "channel";
|
||||
rawId: string;
|
||||
}): SessionConversationResolution | null {
|
||||
}): ResolvedSessionConversation | null {
|
||||
const rawId = params.rawId.trim();
|
||||
if (!rawId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const messaging = getMessagingAdapter(params.channel);
|
||||
const pluginResolved = messaging?.resolveSessionConversation?.({
|
||||
kind: params.kind,
|
||||
rawId,
|
||||
});
|
||||
const pluginResolved = normalizeSessionConversationResolution(
|
||||
messaging?.resolveSessionConversation?.({
|
||||
kind: params.kind,
|
||||
rawId,
|
||||
}),
|
||||
);
|
||||
const resolved =
|
||||
pluginResolved && pluginResolved.id?.trim()
|
||||
? {
|
||||
id: pluginResolved.id.trim(),
|
||||
threadId: pluginResolved.threadId?.trim() || undefined,
|
||||
parentConversationCandidates: dedupeConversationIds(
|
||||
pluginResolved.parentConversationCandidates ?? [],
|
||||
),
|
||||
}
|
||||
: buildGenericConversationResolution(rawId);
|
||||
pluginResolved ??
|
||||
resolveBundledSessionConversationFallback({
|
||||
channel: params.channel,
|
||||
rawId,
|
||||
}) ??
|
||||
buildGenericConversationResolution(rawId);
|
||||
if (!resolved) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const parentConversationCandidates = dedupeConversationIds(
|
||||
messaging?.resolveParentConversationCandidates?.({
|
||||
kind: params.kind,
|
||||
rawId,
|
||||
}) ?? resolved.parentConversationCandidates,
|
||||
pluginResolved?.hasExplicitParentConversationCandidates
|
||||
? resolved.parentConversationCandidates
|
||||
: (messaging?.resolveParentConversationCandidates?.({
|
||||
kind: params.kind,
|
||||
rawId,
|
||||
}) ?? resolved.parentConversationCandidates),
|
||||
);
|
||||
|
||||
return {
|
||||
|
|
@ -130,14 +181,6 @@ export function resolveSessionConversation(params: {
|
|||
return resolveSessionConversationResolution(params);
|
||||
}
|
||||
|
||||
export function resolveParentConversationCandidates(params: {
|
||||
channel: string;
|
||||
kind: "group" | "channel";
|
||||
rawId: string;
|
||||
}): string[] {
|
||||
return resolveSessionConversationResolution(params)?.parentConversationCandidates ?? [];
|
||||
}
|
||||
|
||||
function buildBaseSessionKey(raw: RawSessionConversationRef, id: string): string {
|
||||
return `${raw.prefix}:${id}`;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -398,9 +398,11 @@ export type ChannelThreadingToolContext = {
|
|||
export type ChannelMessagingAdapter = {
|
||||
normalizeTarget?: (raw: string) => string | undefined;
|
||||
/**
|
||||
* Plugin-owned session conversation grammar.
|
||||
* Canonical plugin-owned session conversation grammar.
|
||||
* Use this when the provider encodes thread or scoped-conversation semantics
|
||||
* inside `rawId` (for example Telegram topics or Feishu sender scopes).
|
||||
* Return `parentConversationCandidates` here when you can so parsing and
|
||||
* inheritance stay in one place.
|
||||
*/
|
||||
resolveSessionConversation?: (params: { kind: "group" | "channel"; rawId: string }) => {
|
||||
id: string;
|
||||
|
|
@ -408,8 +410,10 @@ export type ChannelMessagingAdapter = {
|
|||
parentConversationCandidates?: string[];
|
||||
} | null;
|
||||
/**
|
||||
* Plugin-owned inheritance chain for channel-specific conversation ids.
|
||||
* Return broader parent ids in priority order, without repeating `rawId`.
|
||||
* Legacy compatibility hook for parent fallbacks when a plugin does not need
|
||||
* to customize `id` or `threadId`. Core only uses this when
|
||||
* `resolveSessionConversation(...)` does not return
|
||||
* `parentConversationCandidates`.
|
||||
*/
|
||||
resolveParentConversationCandidates?: (params: {
|
||||
kind: "group" | "channel";
|
||||
|
|
|
|||
|
|
@ -132,8 +132,6 @@ export function createSessionConversationTestRegistry() {
|
|||
normalizeTarget: (raw: string) => raw.replace(/^group:/, ""),
|
||||
resolveSessionConversation: ({ rawId }: { rawId: string }) =>
|
||||
parseTelegramTopicConversation(rawId),
|
||||
resolveParentConversationCandidates: ({ rawId }: { rawId: string }) =>
|
||||
parseTelegramTopicConversation(rawId)?.parentConversationCandidates ?? null,
|
||||
},
|
||||
config: {
|
||||
listAccountIds: () => ["default"],
|
||||
|
|
@ -158,8 +156,6 @@ export function createSessionConversationTestRegistry() {
|
|||
normalizeTarget: (raw: string) => raw.replace(/^group:/, ""),
|
||||
resolveSessionConversation: ({ rawId }: { rawId: string }) =>
|
||||
resolveFeishuConversation(rawId),
|
||||
resolveParentConversationCandidates: ({ rawId }: { rawId: string }) =>
|
||||
resolveFeishuConversation(rawId)?.parentConversationCandidates ?? null,
|
||||
},
|
||||
config: {
|
||||
listAccountIds: () => ["default"],
|
||||
|
|
|
|||
Loading…
Reference in New Issue