refactor(routing): centralize inbound last-route policy

This commit is contained in:
Peter Steinberger
2026-03-08 02:02:00 +00:00
parent b2f8f5e4dd
commit 6a8081a7f3
10 changed files with 172 additions and 9 deletions

View File

@@ -2,7 +2,11 @@ import { describe, expect, test, vi } from "vitest";
import type { ChatType } from "../channels/chat-type.js";
import type { OpenClawConfig } from "../config/config.js";
import * as routingBindings from "./bindings.js";
import { resolveAgentRoute } from "./resolve-route.js";
import {
deriveLastRoutePolicy,
resolveAgentRoute,
resolveInboundLastRouteSessionKey,
} from "./resolve-route.js";
describe("resolveAgentRoute", () => {
const resolveDiscordGuildRoute = (cfg: OpenClawConfig) =>
@@ -25,6 +29,7 @@ describe("resolveAgentRoute", () => {
expect(route.agentId).toBe("main");
expect(route.accountId).toBe("default");
expect(route.sessionKey).toBe("agent:main:main");
expect(route.lastRoutePolicy).toBe("main");
expect(route.matchedBy).toBe("default");
});
@@ -47,9 +52,47 @@ describe("resolveAgentRoute", () => {
peer: { kind: "direct", id: "+15551234567" },
});
expect(route.sessionKey).toBe(testCase.expected);
expect(route.lastRoutePolicy).toBe("session");
}
});
test("resolveInboundLastRouteSessionKey follows route policy", () => {
expect(
resolveInboundLastRouteSessionKey({
route: {
mainSessionKey: "agent:main:main",
lastRoutePolicy: "main",
},
sessionKey: "agent:main:discord:direct:user-1",
}),
).toBe("agent:main:main");
expect(
resolveInboundLastRouteSessionKey({
route: {
mainSessionKey: "agent:main:main",
lastRoutePolicy: "session",
},
sessionKey: "agent:main:telegram:atlas:direct:123",
}),
).toBe("agent:main:telegram:atlas:direct:123");
});
test("deriveLastRoutePolicy collapses only main-session routes", () => {
expect(
deriveLastRoutePolicy({
sessionKey: "agent:main:main",
mainSessionKey: "agent:main:main",
}),
).toBe("main");
expect(
deriveLastRoutePolicy({
sessionKey: "agent:main:telegram:direct:123",
mainSessionKey: "agent:main:main",
}),
).toBe("session");
});
test("identityLinks applies to direct-message scopes", () => {
const cases = [
{

View File

@@ -44,6 +44,8 @@ export type ResolvedAgentRoute = {
sessionKey: string;
/** Convenience alias for direct-chat collapse. */
mainSessionKey: string;
/** Which session should receive inbound last-route updates. */
lastRoutePolicy: "main" | "session";
/** Match description for debugging/logging. */
matchedBy:
| "binding.peer"
@@ -58,6 +60,20 @@ export type ResolvedAgentRoute = {
export { DEFAULT_ACCOUNT_ID, DEFAULT_AGENT_ID } from "./session-key.js";
export function deriveLastRoutePolicy(params: {
sessionKey: string;
mainSessionKey: string;
}): ResolvedAgentRoute["lastRoutePolicy"] {
return params.sessionKey === params.mainSessionKey ? "main" : "session";
}
export function resolveInboundLastRouteSessionKey(params: {
route: Pick<ResolvedAgentRoute, "lastRoutePolicy" | "mainSessionKey">;
sessionKey: string;
}): string {
return params.route.lastRoutePolicy === "main" ? params.route.mainSessionKey : params.sessionKey;
}
function normalizeToken(value: string | undefined | null): string {
return (value ?? "").trim().toLowerCase();
}
@@ -662,6 +678,7 @@ export function resolveAgentRoute(input: ResolveAgentRouteInput): ResolvedAgentR
accountId,
sessionKey,
mainSessionKey,
lastRoutePolicy: deriveLastRoutePolicy({ sessionKey, mainSessionKey }),
matchedBy,
};
if (routeCache && routeCacheKey) {