docs: clarify session conversation base routing

This commit is contained in:
Gustavo Madeira Santana 2026-03-31 19:02:03 -04:00
parent efa8632360
commit d3ee04de63
11 changed files with 68 additions and 39 deletions

View File

@ -154,7 +154,7 @@
"exportName": "ChannelMessageActionAdapter",
"kind": "type",
"source": {
"line": 553,
"line": 556,
"path": "src/channels/plugins/types.core.ts"
}
},
@ -163,7 +163,7 @@
"exportName": "ChannelMessageActionContext",
"kind": "type",
"source": {
"line": 517,
"line": 520,
"path": "src/channels/plugins/types.core.ts"
}
},
@ -1170,7 +1170,7 @@
"exportName": "BaseProbeResult",
"kind": "type",
"source": {
"line": 596,
"line": 599,
"path": "src/channels/plugins/types.core.ts"
}
},
@ -1179,7 +1179,7 @@
"exportName": "BaseTokenResolution",
"kind": "type",
"source": {
"line": 602,
"line": 605,
"path": "src/channels/plugins/types.core.ts"
}
},
@ -1224,7 +1224,7 @@
"exportName": "ChannelMessageActionAdapter",
"kind": "type",
"source": {
"line": 553,
"line": 556,
"path": "src/channels/plugins/types.core.ts"
}
},
@ -1233,7 +1233,7 @@
"exportName": "ChannelMessageActionContext",
"kind": "type",
"source": {
"line": 517,
"line": 520,
"path": "src/channels/plugins/types.core.ts"
}
},
@ -1752,7 +1752,7 @@
"exportName": "BaseProbeResult",
"kind": "type",
"source": {
"line": 596,
"line": 599,
"path": "src/channels/plugins/types.core.ts"
}
},
@ -1761,7 +1761,7 @@
"exportName": "BaseTokenResolution",
"kind": "type",
"source": {
"line": 602,
"line": 605,
"path": "src/channels/plugins/types.core.ts"
}
},
@ -1797,7 +1797,7 @@
"exportName": "ChannelAgentPromptAdapter",
"kind": "type",
"source": {
"line": 490,
"line": 493,
"path": "src/channels/plugins/types.core.ts"
}
},
@ -1977,7 +1977,7 @@
"exportName": "ChannelDirectoryEntry",
"kind": "type",
"source": {
"line": 504,
"line": 507,
"path": "src/channels/plugins/types.core.ts"
}
},
@ -1986,7 +1986,7 @@
"exportName": "ChannelDirectoryEntryKind",
"kind": "type",
"source": {
"line": 502,
"line": 505,
"path": "src/channels/plugins/types.core.ts"
}
},
@ -2130,7 +2130,7 @@
"exportName": "ChannelMessageActionAdapter",
"kind": "type",
"source": {
"line": 553,
"line": 556,
"path": "src/channels/plugins/types.core.ts"
}
},
@ -2139,7 +2139,7 @@
"exportName": "ChannelMessageActionContext",
"kind": "type",
"source": {
"line": 517,
"line": 520,
"path": "src/channels/plugins/types.core.ts"
}
},
@ -2274,7 +2274,7 @@
"exportName": "ChannelPollContext",
"kind": "type",
"source": {
"line": 584,
"line": 587,
"path": "src/channels/plugins/types.core.ts"
}
},
@ -2283,7 +2283,7 @@
"exportName": "ChannelPollResult",
"kind": "type",
"source": {
"line": 575,
"line": 578,
"path": "src/channels/plugins/types.core.ts"
}
},
@ -2427,7 +2427,7 @@
"exportName": "ChannelToolSend",
"kind": "type",
"source": {
"line": 546,
"line": 549,
"path": "src/channels/plugins/types.core.ts"
}
},
@ -3738,7 +3738,7 @@
"exportName": "ChannelMessageActionContext",
"kind": "type",
"source": {
"line": 517,
"line": 520,
"path": "src/channels/plugins/types.core.ts"
}
},

File diff suppressed because one or more lines are too long

View File

@ -38,7 +38,9 @@ generic `:thread:` bookkeeping, and dispatch.
If your platform stores extra scope inside conversation ids, keep that parsing
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`.
id, explicit `baseConversationId`, and any `parentConversationCandidates`.
When you return `parentConversationCandidates`, keep them ordered from the
narrowest parent to the broadest/base conversation.
Bundled plugins that need the same parsing before the channel registry boots
can also expose a top-level `session-key-api.ts` file with a matching

View File

@ -197,6 +197,7 @@ describe("feishuPlugin messaging", () => {
}),
).toEqual({
id: "oc_group_chat:topic:om_topic_root:sender:ou_topic_user",
baseConversationId: "oc_group_chat",
parentConversationCandidates: ["oc_group_chat:topic:om_topic_root", "oc_group_chat"],
});
expect(
@ -206,6 +207,7 @@ describe("feishuPlugin messaging", () => {
}),
).toEqual({
id: "oc_group_chat:topic:om_topic_root",
baseConversationId: "oc_group_chat",
parentConversationCandidates: ["oc_group_chat"],
});
expect(
@ -215,6 +217,7 @@ describe("feishuPlugin messaging", () => {
}),
).toEqual({
id: "oc_group_chat:topic:om_topic_root:sender:ou_topic_user",
baseConversationId: "oc_group_chat",
parentConversationCandidates: ["oc_group_chat:topic:om_topic_root", "oc_group_chat"],
});
});

View File

@ -34,6 +34,7 @@ export function resolveFeishuSessionConversation(params: {
}
return {
id: parsed.canonicalConversationId,
baseConversationId: parsed.chatId,
parentConversationCandidates: resolveFeishuParentConversationCandidates(
parsed.canonicalConversationId,
),

View File

@ -197,6 +197,7 @@ describe("telegramPlugin messaging", () => {
).toEqual({
id: "-1001",
threadId: "77",
baseConversationId: "-1001",
parentConversationCandidates: ["-1001"],
});
expect(
@ -207,6 +208,7 @@ describe("telegramPlugin messaging", () => {
).toEqual({
id: "-1001",
threadId: "77",
baseConversationId: "-1001",
parentConversationCandidates: ["-1001"],
});
expect(

View File

@ -11,6 +11,7 @@ export function resolveTelegramSessionConversation(params: {
return {
id: parsed.chatId,
threadId: parsed.topicId,
baseConversationId: parsed.chatId,
parentConversationCandidates: [parsed.chatId],
};
}

View File

@ -141,8 +141,7 @@ function resolveGroupContextFromSessionKey(sessionKey?: string | null): {
}
const resolvedConversation = resolveSessionConversationRef(raw);
if (resolvedConversation) {
const groupId =
resolvedConversation.parentConversationCandidates.at(-1) ?? resolvedConversation.id;
const groupId = resolvedConversation.baseConversationId;
if (!groupId) {
return {};
}

View File

@ -24,6 +24,7 @@ describe("session conversation routing", () => {
id: "general",
threadId: "1699999999.0001",
baseSessionKey: "agent:main:slack:channel:general",
baseConversationId: "general",
parentConversationCandidates: ["general"],
});
});
@ -36,6 +37,7 @@ describe("session conversation routing", () => {
id: "-100123",
threadId: "77",
baseSessionKey: "agent:main:telegram:group:-100123",
baseConversationId: "-100123",
parentConversationCandidates: ["-100123"],
});
expect(resolveSessionThreadInfo("agent:main:telegram:group:-100123:topic:77")).toEqual({
@ -57,6 +59,7 @@ describe("session conversation routing", () => {
id: "-100123",
threadId: "77",
baseSessionKey: "agent:main:telegram:group:-100123",
baseConversationId: "-100123",
parentConversationCandidates: ["-100123"],
});
});
@ -76,6 +79,7 @@ describe("session conversation routing", () => {
threadId: undefined,
baseSessionKey:
"agent:main:feishu:group:oc_group_chat:topic:om_topic_root:sender:ou_topic_user",
baseConversationId: "oc_group_chat",
parentConversationCandidates: ["oc_group_chat:topic:om_topic_root", "oc_group_chat"],
});
});
@ -93,6 +97,7 @@ describe("session conversation routing", () => {
threadId: undefined,
baseSessionKey:
"agent:main:feishu:group:oc_group_chat:topic:om_topic_root:sender:ou_topic_user",
baseConversationId: "oc_group_chat",
parentConversationCandidates: ["oc_group_chat:topic:om_topic_root", "oc_group_chat"],
});
expect(
@ -140,6 +145,7 @@ describe("session conversation routing", () => {
).toEqual({
id: "room:sender:user",
threadId: undefined,
baseConversationId: "room",
parentConversationCandidates: ["room"],
});
});

View File

@ -14,6 +14,7 @@ import { getChannelPlugin, normalizeChannelId as normalizeAnyChannelId } from ".
export type ResolvedSessionConversation = {
id: string;
threadId: string | undefined;
baseConversationId: string;
parentConversationCandidates: string[];
};
@ -24,12 +25,14 @@ export type ResolvedSessionConversationRef = {
id: string;
threadId: string | undefined;
baseSessionKey: string;
baseConversationId: string;
parentConversationCandidates: string[];
};
type SessionConversationHookResult = {
id: string;
threadId?: string | null;
baseConversationId?: string | null;
parentConversationCandidates?: string[];
};
@ -100,6 +103,7 @@ function buildGenericConversationResolution(rawId: string): ResolvedSessionConve
return {
id,
threadId: parsed.threadId,
baseConversationId: id,
parentConversationCandidates: dedupeConversationIds(
parsed.threadId ? [parsed.baseSessionKey] : [],
),
@ -116,6 +120,10 @@ function normalizeSessionConversationResolution(
return {
id: resolved.id.trim(),
threadId: resolved.threadId?.trim() || undefined,
baseConversationId:
resolved.baseConversationId?.trim() ||
dedupeConversationIds(resolved.parentConversationCandidates ?? []).at(-1) ||
resolved.id.trim(),
parentConversationCandidates: dedupeConversationIds(
resolved.parentConversationCandidates ?? [],
),
@ -197,9 +205,12 @@ function resolveSessionConversationResolution(params: {
rawId,
}) ?? resolved.parentConversationCandidates),
);
const baseConversationId =
parentConversationCandidates.at(-1) ?? resolved.baseConversationId ?? resolved.id;
return {
...resolved,
baseConversationId,
parentConversationCandidates,
};
}
@ -236,6 +247,7 @@ export function resolveSessionConversationRef(
id: resolved.id,
threadId: resolved.threadId,
baseSessionKey: buildBaseSessionKey(raw, resolved.id),
baseConversationId: resolved.baseConversationId,
parentConversationCandidates: resolved.parentConversationCandidates,
};
}

View File

@ -401,14 +401,17 @@ export type ChannelMessagingAdapter = {
* 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.
* Return `baseConversationId` and `parentConversationCandidates` here when
* you can so parsing and inheritance stay in one place.
* `parentConversationCandidates`, when present, should be ordered from the
* narrowest parent to the broadest/base conversation.
* Bundled plugins that need the same grammar before runtime bootstrap can
* mirror this contract through a top-level `session-key-api.ts` surface.
*/
resolveSessionConversation?: (params: { kind: "group" | "channel"; rawId: string }) => {
id: string;
threadId?: string | null;
baseConversationId?: string | null;
parentConversationCandidates?: string[];
} | null;
/**