fix(gateway): normalize session key to canonical form before store writes

Ensure 'main' alias is always stored as 'agent:main:main' to prevent
duplicate entries. Also update loadSessionEntry to check both forms
when looking up entries.

Fixes duplicate main sessions in session store.
This commit is contained in:
user
2026-01-11 07:06:25 +00:00
committed by Peter Steinberger
parent 7d6f17d77f
commit d4e9f23ee9
4 changed files with 68 additions and 15 deletions

View File

@@ -24,6 +24,7 @@ import { buildConfigSchema } from "../config/schema.js";
import { import {
loadSessionStore, loadSessionStore,
mergeSessionEntry, mergeSessionEntry,
resolveAgentMainSessionKey,
resolveMainSessionKeyFromConfig, resolveMainSessionKeyFromConfig,
type SessionEntry, type SessionEntry,
saveSessionStore, saveSessionStore,
@@ -35,7 +36,10 @@ import {
} from "../infra/voicewake.js"; } from "../infra/voicewake.js";
import { clearCommandLane } from "../process/command-queue.js"; import { clearCommandLane } from "../process/command-queue.js";
import { normalizeProviderId } from "../providers/plugins/index.js"; import { normalizeProviderId } from "../providers/plugins/index.js";
import { normalizeMainKey } from "../routing/session-key.js"; import {
normalizeMainKey,
resolveAgentIdFromSessionKey,
} from "../routing/session-key.js";
import { defaultRuntime } from "../runtime.js"; import { defaultRuntime } from "../runtime.js";
import { import {
abortChatRunById, abortChatRunById,
@@ -918,8 +922,14 @@ export function createBridgeHandlers(ctx: BridgeHandlersContext) {
clientRunId, clientRunId,
}); });
// Normalize short main key alias to canonical form before store write
const agentId = resolveAgentIdFromSessionKey(p.sessionKey);
const mainSessionKey = resolveAgentMainSessionKey({ cfg, agentId });
const rawMainKey = normalizeMainKey(cfg.session?.mainKey);
const storeKey =
p.sessionKey === rawMainKey ? mainSessionKey : p.sessionKey;
if (store) { if (store) {
store[p.sessionKey] = sessionEntry; store[storeKey] = sessionEntry;
if (storePath) { if (storePath) {
await saveSessionStore(storePath, store); await saveSessionStore(storePath, store);
} }
@@ -1032,12 +1042,18 @@ export function createBridgeHandlers(ctx: BridgeHandlersContext) {
if (text.length > 20_000) return; if (text.length > 20_000) return;
const sessionKeyRaw = const sessionKeyRaw =
typeof obj.sessionKey === "string" ? obj.sessionKey.trim() : ""; typeof obj.sessionKey === "string" ? obj.sessionKey.trim() : "";
const mainKey = normalizeMainKey(loadConfig().session?.mainKey); const cfg = loadConfig();
const sessionKey = sessionKeyRaw.length > 0 ? sessionKeyRaw : mainKey; const rawMainKey = normalizeMainKey(cfg.session?.mainKey);
const sessionKey = sessionKeyRaw.length > 0 ? sessionKeyRaw : rawMainKey;
const { storePath, store, entry } = loadSessionEntry(sessionKey); const { storePath, store, entry } = loadSessionEntry(sessionKey);
// Normalize short main key alias to canonical form before store write
const agentId = resolveAgentIdFromSessionKey(sessionKey);
const mainSessionKey = resolveAgentMainSessionKey({ cfg, agentId });
const storeKey =
sessionKey === rawMainKey ? mainSessionKey : sessionKey;
const now = Date.now(); const now = Date.now();
const sessionId = entry?.sessionId ?? randomUUID(); const sessionId = entry?.sessionId ?? randomUUID();
store[sessionKey] = { store[storeKey] = {
sessionId, sessionId,
updatedAt: now, updatedAt: now,
thinkingLevel: entry?.thinkingLevel, thinkingLevel: entry?.thinkingLevel,
@@ -1112,9 +1128,19 @@ export function createBridgeHandlers(ctx: BridgeHandlersContext) {
const sessionKey = const sessionKey =
sessionKeyRaw.length > 0 ? sessionKeyRaw : `node-${nodeId}`; sessionKeyRaw.length > 0 ? sessionKeyRaw : `node-${nodeId}`;
const { storePath, store, entry } = loadSessionEntry(sessionKey); const { storePath, store, entry } = loadSessionEntry(sessionKey);
// Normalize short main key alias to canonical form before store write
const nodeCfg = loadConfig();
const nodeAgentId = resolveAgentIdFromSessionKey(sessionKey);
const nodeMainSessionKey = resolveAgentMainSessionKey({
cfg: nodeCfg,
agentId: nodeAgentId,
});
const nodeRawMainKey = normalizeMainKey(nodeCfg.session?.mainKey);
const nodeStoreKey =
sessionKey === nodeRawMainKey ? nodeMainSessionKey : sessionKey;
const now = Date.now(); const now = Date.now();
const sessionId = entry?.sessionId ?? randomUUID(); const sessionId = entry?.sessionId ?? randomUUID();
store[sessionKey] = { store[nodeStoreKey] = {
sessionId, sessionId,
updatedAt: now, updatedAt: now,
thinkingLevel: entry?.thinkingLevel, thinkingLevel: entry?.thinkingLevel,

View File

@@ -189,12 +189,6 @@ export const agentHandlers: GatewayRequestHandlers = {
); );
return; return;
} }
if (store) {
store[requestedSessionKey] = nextEntry;
if (storePath) {
await saveSessionStore(storePath, store);
}
}
resolvedSessionId = sessionId; resolvedSessionId = sessionId;
const agentId = resolveAgentIdFromSessionKey(requestedSessionKey); const agentId = resolveAgentIdFromSessionKey(requestedSessionKey);
const mainSessionKey = resolveAgentMainSessionKey({ const mainSessionKey = resolveAgentMainSessionKey({
@@ -202,6 +196,15 @@ export const agentHandlers: GatewayRequestHandlers = {
agentId, agentId,
}); });
const rawMainKey = normalizeMainKey(cfg.session?.mainKey); const rawMainKey = normalizeMainKey(cfg.session?.mainKey);
// Normalize short main key alias to canonical form before store write
const storeKey =
requestedSessionKey === rawMainKey ? mainSessionKey : requestedSessionKey;
if (store) {
store[storeKey] = nextEntry;
if (storePath) {
await saveSessionStore(storePath, store);
}
}
if ( if (
requestedSessionKey === mainSessionKey || requestedSessionKey === mainSessionKey ||
requestedSessionKey === rawMainKey requestedSessionKey === rawMainKey

View File

@@ -3,7 +3,15 @@ import { randomUUID } from "node:crypto";
import { resolveThinkingDefault } from "../../agents/model-selection.js"; import { resolveThinkingDefault } from "../../agents/model-selection.js";
import { resolveAgentTimeoutMs } from "../../agents/timeout.js"; import { resolveAgentTimeoutMs } from "../../agents/timeout.js";
import { agentCommand } from "../../commands/agent.js"; import { agentCommand } from "../../commands/agent.js";
import { mergeSessionEntry, saveSessionStore } from "../../config/sessions.js"; import {
mergeSessionEntry,
resolveAgentMainSessionKey,
saveSessionStore,
} from "../../config/sessions.js";
import {
normalizeMainKey,
resolveAgentIdFromSessionKey,
} from "../../routing/session-key.js";
import { registerAgentRunContext } from "../../infra/agent-events.js"; import { registerAgentRunContext } from "../../infra/agent-events.js";
import { defaultRuntime } from "../../runtime.js"; import { defaultRuntime } from "../../runtime.js";
import { resolveSendPolicy } from "../../sessions/send-policy.js"; import { resolveSendPolicy } from "../../sessions/send-policy.js";
@@ -306,8 +314,14 @@ export const chatHandlers: GatewayRequestHandlers = {
clientRunId, clientRunId,
}); });
// Normalize short main key alias to canonical form before store write
const agentId = resolveAgentIdFromSessionKey(p.sessionKey);
const mainSessionKey = resolveAgentMainSessionKey({ cfg, agentId });
const rawMainKey = normalizeMainKey(cfg.session?.mainKey);
const storeKey =
p.sessionKey === rawMainKey ? mainSessionKey : p.sessionKey;
if (store) { if (store) {
store[p.sessionKey] = sessionEntry; store[storeKey] = sessionEntry;
if (storePath) { if (storePath) {
await saveSessionStore(storePath, store); await saveSessionStore(storePath, store);
} }

View File

@@ -15,6 +15,7 @@ import {
buildGroupDisplayName, buildGroupDisplayName,
loadSessionStore, loadSessionStore,
resolveAgentIdFromSessionKey, resolveAgentIdFromSessionKey,
resolveAgentMainSessionKey,
resolveSessionTranscriptPath, resolveSessionTranscriptPath,
resolveStorePath, resolveStorePath,
type SessionEntry, type SessionEntry,
@@ -172,7 +173,16 @@ export function loadSessionEntry(sessionKey: string) {
const store = loadSessionStore(storePath); const store = loadSessionStore(storePath);
const parsed = parseAgentSessionKey(sessionKey); const parsed = parseAgentSessionKey(sessionKey);
const legacyKey = parsed?.rest; const legacyKey = parsed?.rest;
const entry = store[sessionKey] ?? (legacyKey ? store[legacyKey] : undefined); // Also try the canonical key if sessionKey is the short mainKey alias
const rawMainKey = normalizeMainKey(sessionCfg?.mainKey);
const canonicalKey =
sessionKey === rawMainKey
? resolveAgentMainSessionKey({ cfg, agentId })
: undefined;
const entry =
store[sessionKey] ??
(legacyKey ? store[legacyKey] : undefined) ??
(canonicalKey ? store[canonicalKey] : undefined);
return { cfg, storePath, store, entry }; return { cfg, storePath, store, entry };
} }