diff --git a/src/tui/tui-pty-harness.e2e.test.ts b/src/tui/tui-pty-harness.e2e.test.ts index dc1a7e52f31a..3dc889765b19 100644 --- a/src/tui/tui-pty-harness.e2e.test.ts +++ b/src/tui/tui-pty-harness.e2e.test.ts @@ -916,6 +916,34 @@ describe.sequential("TUI PTY harness", () => { TEST_TIMEOUT_MS, ); + it.each([ + { + sessionKey: "agent:main:matrix:channel:!MixedRoomAbCdEf:example.org", + message: "mixed-case matrix session identity proof", + }, + { + sessionKey: "agent:main:signal:group:AbC123=", + message: "mixed-case signal session identity proof", + }, + ])( + "preserves provider-owned identity when selecting $sessionKey in the terminal", + async ({ sessionKey, message }) => { + await fixture.run.write(`/session ${sessionKey}\r`, { delay: false }); + await fixture.waitForLogEntry( + (entry) => + entry.method === "loadHistory" && objectFieldEquals(entry, "sessionKey", sessionKey), + ); + + await fixture.run.write(`${message}\r`, { delay: false }); + const sent = await fixture.waitForLogEntry( + (entry) => entry.method === "sendChat" && objectFieldEquals(entry, "message", message), + ); + + expect(sent.payload).toMatchObject({ sessionKey, message }); + }, + TEST_TIMEOUT_MS, + ); + it( "creates a backend session from /new and adopts its canonical key", async () => { diff --git a/src/tui/tui.test.ts b/src/tui/tui.test.ts index a650403151b7..b4e6a47ecd72 100644 --- a/src/tui/tui.test.ts +++ b/src/tui/tui.test.ts @@ -186,6 +186,50 @@ describe("resolveTuiSessionKey", () => { ).toBe("agent:ops:incident"); }); + it.each([ + { + raw: "agent:main:matrix:channel:!MixedRoomAbCdEf:example.org", + expected: "agent:main:matrix:channel:!MixedRoomAbCdEf:example.org", + }, + { + raw: "Matrix:Channel:!MixedRoomAbCdEf:example.org", + expected: "agent:main:matrix:channel:!MixedRoomAbCdEf:example.org", + }, + { + raw: "Agent:Main:Matrix:Channel:!MixedRoomAbCdEf:example.org:Thread:$EventAbCdEf", + expected: "agent:main:matrix:channel:!MixedRoomAbCdEf:example.org:thread:$EventAbCdEf", + }, + { + raw: "Agent:Ops:Matrix:Channel:!MixedRoomAbCdEf:example.org", + expected: "agent:ops:matrix:channel:!MixedRoomAbCdEf:example.org", + }, + { + raw: "agent:main:signal:group:AbC123=", + expected: "agent:main:signal:group:AbC123=", + }, + { + raw: "Agent:Ops:Signal:Group:AbC123=", + expected: "agent:ops:signal:group:AbC123=", + }, + { + raw: "Signal:Group:AbC123=", + expected: "agent:main:signal:group:AbC123=", + }, + { + raw: "Telegram:Group:MixedHandle", + expected: "agent:main:telegram:group:mixedhandle", + }, + ])("preserves canonical provider-owned session identity for $raw", ({ raw, expected }) => { + expect( + resolveTuiSessionKey({ + raw, + sessionScope: "per-sender", + currentAgentId: "main", + sessionMainKey: "main", + }), + ).toBe(expected); + }); + it("lowercases session keys with uppercase characters", () => { // Uppercase in agent-prefixed form expect( diff --git a/src/tui/tui.ts b/src/tui/tui.ts index 18b4207de764..6134d717a23d 100644 --- a/src/tui/tui.ts +++ b/src/tui/tui.ts @@ -13,7 +13,6 @@ import { Text, TUI, } from "@earendil-works/pi-tui"; -import { normalizeLowercaseStringOrEmpty } from "@openclaw/normalization-core/string-coerce"; import type { CommandEntry } from "../../packages/gateway-protocol/src/index.js"; import { resolveAgentIdByWorkspacePath, resolveDefaultAgentId } from "../agents/agent-scope.js"; import { normalizeThinkLevel } from "../auto-reply/thinking.shared.js"; @@ -35,6 +34,7 @@ import { normalizeAgentId, normalizeMainKey, parseAgentSessionKey, + toAgentStoreSessionKey, } from "../routing/session-key.js"; import { getSlashCommands, shouldSubmitExactArgumentCompletion } from "./commands.js"; import { ChatLog } from "./components/chat-log.js"; @@ -213,10 +213,11 @@ export function resolveTuiSessionKey(params: { if (trimmed === "global" || trimmed === "unknown") { return trimmed; } - if (trimmed.startsWith("agent:")) { - return normalizeLowercaseStringOrEmpty(trimmed); - } - return `agent:${params.currentAgentId}:${normalizeLowercaseStringOrEmpty(trimmed)}`; + return toAgentStoreSessionKey({ + agentId: params.currentAgentId, + requestKey: trimmed, + mainKey: params.sessionMainKey, + }); } export function resolveInitialTuiAgentId(params: {