fix: normalize binding context restore ids

This commit is contained in:
Tak Hoffman
2026-04-10 19:32:27 -05:00
parent 7d3062270c
commit 754aaa2670
4 changed files with 102 additions and 4 deletions

View File

@@ -58,4 +58,41 @@ describe("resolveConversationBindingContext", () => {
conversationId: "U1234567890abcdef1234567890abcdef",
});
});
it("normalizes provider-resolved conversation ids before returning binding context", () => {
setActivePluginRegistry(
createTestRegistry([
{
pluginId: "line",
source: "test",
plugin: {
...createChannelTestPluginBase({
id: "line",
label: "LINE",
}),
bindings: {
resolveCommandConversation: () => ({
conversationId: " user:U1234567890abcdef1234567890abcdef ",
parentConversationId: " room:R1234567890abcdef1234567890abcd ",
}),
},
},
},
]),
);
expect(
resolveConversationBindingContext({
cfg: {} as OpenClawConfig,
channel: "line",
accountId: " default ",
originatingTo: "ignored",
}),
).toEqual({
channel: "line",
accountId: "default",
conversationId: "user:U1234567890abcdef1234567890abcdef",
parentConversationId: "room:R1234567890abcdef1234567890abcd",
});
});
});

View File

@@ -160,16 +160,23 @@ export function resolveConversationBindingContext(
fallbackTo: params.fallbackTo ?? undefined,
});
if (resolvedByProvider?.conversationId) {
const providerConversationId = normalizeOptionalString(resolvedByProvider.conversationId);
if (!providerConversationId) {
return null;
}
const providerParentConversationId = normalizeOptionalString(
resolvedByProvider.parentConversationId,
);
const resolvedParentConversationId =
shouldDefaultParentConversationToSelf(loadedPlugin) &&
!threadId &&
!resolvedByProvider.parentConversationId
? resolvedByProvider.conversationId
: resolvedByProvider.parentConversationId;
!providerParentConversationId
? providerConversationId
: providerParentConversationId;
return {
channel,
accountId,
conversationId: resolvedByProvider.conversationId,
conversationId: providerConversationId,
...(resolvedParentConversationId
? { parentConversationId: resolvedParentConversationId }
: {}),

View File

@@ -8,6 +8,7 @@ import {
__testing,
bindGenericCurrentConversation,
getGenericCurrentConversationBindingCapabilities,
listGenericCurrentConversationBindingsBySession,
resolveGenericCurrentConversationBinding,
touchGenericCurrentConversationBinding,
unbindGenericCurrentConversationBindings,
@@ -124,6 +125,54 @@ describe("generic current-conversation bindings", () => {
});
});
it("normalizes persisted target session keys on reload", async () => {
const filePath = __testing.resolveBindingsFilePath();
await fs.mkdir(path.dirname(filePath), { recursive: true });
await fs.writeFile(
filePath,
JSON.stringify({
version: 1,
bindings: [
{
bindingId: "generic:slack\u241fdefault\u241f\u241fuser:U123",
targetSessionKey: " agent:codex:acp:slack-dm ",
targetKind: "session",
conversation: {
channel: "slack",
accountId: "default",
conversationId: "user:U123",
},
status: "active",
boundAt: 1234,
metadata: {
label: "slack-dm",
},
},
],
}),
);
const resolved = resolveGenericCurrentConversationBinding({
channel: "slack",
accountId: "default",
conversationId: "user:U123",
});
expect(resolved).toMatchObject({
bindingId: "generic:slack\u241fdefault\u241f\u241fuser:U123",
targetSessionKey: "agent:codex:acp:slack-dm",
metadata: expect.objectContaining({
label: "slack-dm",
}),
});
expect(listGenericCurrentConversationBindingsBySession("agent:codex:acp:slack-dm")).toEqual([
expect.objectContaining({
bindingId: "generic:slack\u241fdefault\u241f\u241fuser:U123",
targetSessionKey: "agent:codex:acp:slack-dm",
}),
]);
});
it("drops self-parent conversation refs when storing generic current bindings", async () => {
const bound = await bindGenericCurrentConversation({
targetSessionKey: "agent:codex:acp:telegram-dm",

View File

@@ -76,9 +76,14 @@ function loadBindingsIntoMemory(): void {
continue;
}
const conversation = normalizeConversationRef(record.conversation);
const targetSessionKey = record.targetSessionKey?.trim() ?? "";
if (!targetSessionKey) {
continue;
}
bindingsByConversationKey.set(buildConversationKey(conversation), {
...record,
bindingId: buildBindingId(conversation),
targetSessionKey,
conversation,
});
}