From e29d3709696bf920cff47d2ee8cf9cb7a020b7ca Mon Sep 17 00:00:00 2001 From: Gustavo Madeira Santana Date: Sun, 5 Apr 2026 17:42:05 -0400 Subject: [PATCH] Gateway: keep outbound session metadata in owner store --- src/gateway/server-methods/send.ts | 1 - .../outbound/message-action-threading.ts | 2 - src/infra/outbound/outbound-session.test.ts | 54 ++++++++++++++++++- src/infra/outbound/outbound-session.ts | 4 +- 4 files changed, 54 insertions(+), 7 deletions(-) diff --git a/src/gateway/server-methods/send.ts b/src/gateway/server-methods/send.ts index 2f53144a458..00167b9da24 100644 --- a/src/gateway/server-methods/send.ts +++ b/src/gateway/server-methods/send.ts @@ -253,7 +253,6 @@ export const sendHandlers: GatewayRequestHandlers = { if (outboundRoute) { await ensureOutboundSessionEntry({ cfg, - agentId: effectiveAgentId, channel, accountId, route: outboundRoute, diff --git a/src/infra/outbound/message-action-threading.ts b/src/infra/outbound/message-action-threading.ts index 95e59eca290..a882470c64e 100644 --- a/src/infra/outbound/message-action-threading.ts +++ b/src/infra/outbound/message-action-threading.ts @@ -56,7 +56,6 @@ export async function prepareOutboundMirrorRoute(params: { ) => Promise; ensureOutboundSessionEntry: (params: { cfg: OpenClawConfig; - agentId: string; channel: ChannelId; accountId?: string | null; route: OutboundSessionRoute; @@ -90,7 +89,6 @@ export async function prepareOutboundMirrorRoute(params: { if (outboundRoute && params.agentId && !params.dryRun) { await params.ensureOutboundSessionEntry({ cfg: params.cfg, - agentId: params.agentId, channel: params.channel, accountId: params.accountId, route: outboundRoute, diff --git a/src/infra/outbound/outbound-session.test.ts b/src/infra/outbound/outbound-session.test.ts index 917327ff392..afccdf0698a 100644 --- a/src/infra/outbound/outbound-session.test.ts +++ b/src/infra/outbound/outbound-session.test.ts @@ -1,10 +1,24 @@ -import { beforeEach, describe, expect, it } from "vitest"; +import { beforeEach, describe, expect, it, vi } from "vitest"; import type { OpenClawConfig } from "../../config/config.js"; -import { resolveOutboundSessionRoute } from "./outbound-session.js"; +import { ensureOutboundSessionEntry, resolveOutboundSessionRoute } from "./outbound-session.js"; import { setMinimalOutboundSessionPluginRegistryForTests } from "./outbound-session.test-helpers.js"; +const mocks = vi.hoisted(() => ({ + recordSessionMetaFromInbound: vi.fn(async () => ({ ok: true })), + resolveStorePath: vi.fn( + (_store: unknown, params?: { agentId?: string }) => `/stores/${params?.agentId ?? "main"}.json`, + ), +})); + +vi.mock("../../config/sessions/inbound.runtime.js", () => ({ + recordSessionMetaFromInbound: mocks.recordSessionMetaFromInbound, + resolveStorePath: mocks.resolveStorePath, +})); + describe("resolveOutboundSessionRoute", () => { beforeEach(() => { + mocks.recordSessionMetaFromInbound.mockClear(); + mocks.resolveStorePath.mockClear(); setMinimalOutboundSessionPluginRegistryForTests(); }); @@ -394,3 +408,39 @@ describe("resolveOutboundSessionRoute", () => { ).rejects.toThrow(/Ambiguous Discord recipient/); }); }); + +describe("ensureOutboundSessionEntry", () => { + beforeEach(() => { + mocks.recordSessionMetaFromInbound.mockClear(); + mocks.resolveStorePath.mockClear(); + }); + + it("persists metadata in the owning session store for the route session key", async () => { + await ensureOutboundSessionEntry({ + cfg: { + session: { + store: "/stores/{agentId}.json", + }, + } as OpenClawConfig, + channel: "slack", + route: { + sessionKey: "agent:main:slack:channel:c1", + baseSessionKey: "agent:work:slack:channel:resolved", + peer: { kind: "channel", id: "c1" }, + chatType: "channel", + from: "slack:channel:C1", + to: "channel:C1", + }, + }); + + expect(mocks.resolveStorePath).toHaveBeenCalledWith("/stores/{agentId}.json", { + agentId: "main", + }); + expect(mocks.recordSessionMetaFromInbound).toHaveBeenCalledWith( + expect.objectContaining({ + storePath: "/stores/main.json", + sessionKey: "agent:main:slack:channel:c1", + }), + ); + }); +}); diff --git a/src/infra/outbound/outbound-session.ts b/src/infra/outbound/outbound-session.ts index 1d7ecdc0840..90588d1d3bd 100644 --- a/src/infra/outbound/outbound-session.ts +++ b/src/infra/outbound/outbound-session.ts @@ -8,6 +8,7 @@ import { resolveStorePath, } from "../../config/sessions/inbound.runtime.js"; import { buildAgentSessionKey, type RoutePeer } from "../../routing/resolve-route.js"; +import { resolveAgentIdFromSessionKey } from "../../routing/session-key.js"; import type { ResolvedMessagingTarget } from "./target-resolver.js"; export type OutboundSessionRoute = { @@ -148,13 +149,12 @@ export async function resolveOutboundSessionRoute( export async function ensureOutboundSessionEntry(params: { cfg: OpenClawConfig; - agentId: string; channel: ChannelId; accountId?: string | null; route: OutboundSessionRoute; }): Promise { const storePath = resolveStorePath(params.cfg.session?.store, { - agentId: params.agentId, + agentId: resolveAgentIdFromSessionKey(params.route.sessionKey), }); const ctx: MsgContext = { From: params.route.from,