refactor: normalize main session key handling

This commit is contained in:
Peter Steinberger
2026-01-09 22:30:10 +01:00
parent 83270f98f7
commit 6c7a27c010
14 changed files with 40 additions and 30 deletions

View File

@@ -19,7 +19,7 @@ import {
loadConfig, loadConfig,
STATE_DIR_CLAWDBOT, STATE_DIR_CLAWDBOT,
} from "../config/config.js"; } from "../config/config.js";
import { normalizeAgentId } from "../routing/session-key.js"; import { normalizeAgentId, normalizeMainKey } from "../routing/session-key.js";
import { defaultRuntime } from "../runtime.js"; import { defaultRuntime } from "../runtime.js";
import { resolveUserPath } from "../utils.js"; import { resolveUserPath } from "../utils.js";
import { import {
@@ -1045,7 +1045,7 @@ export async function resolveSandboxContext(params: {
if (!rawSessionKey) return null; if (!rawSessionKey) return null;
const agentId = resolveAgentIdFromSessionKey(rawSessionKey); const agentId = resolveAgentIdFromSessionKey(rawSessionKey);
const cfg = resolveSandboxConfigForAgent(params.config, agentId); const cfg = resolveSandboxConfigForAgent(params.config, agentId);
const mainKey = params.config?.session?.mainKey?.trim() || "main"; const mainKey = normalizeMainKey(params.config?.session?.mainKey);
if (!shouldSandboxSession(cfg, rawSessionKey, mainKey)) return null; if (!shouldSandboxSession(cfg, rawSessionKey, mainKey)) return null;
await maybePruneSandboxes(cfg); await maybePruneSandboxes(cfg);
@@ -1121,7 +1121,7 @@ export async function ensureSandboxWorkspaceForSession(params: {
if (!rawSessionKey) return null; if (!rawSessionKey) return null;
const agentId = resolveAgentIdFromSessionKey(rawSessionKey); const agentId = resolveAgentIdFromSessionKey(rawSessionKey);
const cfg = resolveSandboxConfigForAgent(params.config, agentId); const cfg = resolveSandboxConfigForAgent(params.config, agentId);
const mainKey = params.config?.session?.mainKey?.trim() || "main"; const mainKey = normalizeMainKey(params.config?.session?.mainKey);
if (!shouldSandboxSession(cfg, rawSessionKey, mainKey)) return null; if (!shouldSandboxSession(cfg, rawSessionKey, mainKey)) return null;
const agentWorkspaceDir = resolveUserPath( const agentWorkspaceDir = resolveUserPath(

View File

@@ -1,4 +1,5 @@
import type { ClawdbotConfig } from "../../config/config.js"; import type { ClawdbotConfig } from "../../config/config.js";
import { normalizeMainKey } from "../../routing/session-key.js";
export type SessionKind = "main" | "group" | "cron" | "hook" | "node" | "other"; export type SessionKind = "main" | "group" | "cron" | "hook" | "node" | "other";
@@ -8,7 +9,7 @@ function normalizeKey(value?: string) {
} }
export function resolveMainSessionAlias(cfg: ClawdbotConfig) { export function resolveMainSessionAlias(cfg: ClawdbotConfig) {
const mainKey = normalizeKey(cfg.session?.mainKey) ?? "main"; const mainKey = normalizeMainKey(cfg.session?.mainKey);
const scope = cfg.session?.scope ?? "per-sender"; const scope = cfg.session?.scope ?? "per-sender";
const alias = scope === "global" ? "global" : mainKey; const alias = scope === "global" ? "global" : mainKey;
return { mainKey, alias, scope }; return { mainKey, alias, scope };

View File

@@ -29,6 +29,7 @@ import {
import { resolveSessionFilePath } from "../config/sessions.js"; import { resolveSessionFilePath } from "../config/sessions.js";
import { logVerbose } from "../globals.js"; import { logVerbose } from "../globals.js";
import { clearCommandLane, getQueueSize } from "../process/command-queue.js"; import { clearCommandLane, getQueueSize } from "../process/command-queue.js";
import { normalizeMainKey } from "../routing/session-key.js";
import { defaultRuntime } from "../runtime.js"; import { defaultRuntime } from "../runtime.js";
import { resolveCommandAuthorization } from "./command-auth.js"; import { resolveCommandAuthorization } from "./command-auth.js";
import { hasControlCommand } from "./command-detection.js"; import { hasControlCommand } from "./command-detection.js";
@@ -780,7 +781,7 @@ export async function getReplyFromConfig(
const isGroupSession = const isGroupSession =
sessionEntry?.chatType === "group" || sessionEntry?.chatType === "room"; sessionEntry?.chatType === "group" || sessionEntry?.chatType === "room";
const isMainSession = const isMainSession =
!isGroupSession && sessionKey === (sessionCfg?.mainKey ?? "main"); !isGroupSession && sessionKey === normalizeMainKey(sessionCfg?.mainKey);
prefixedBodyBase = await prependSystemEvents({ prefixedBodyBase = await prependSystemEvents({
cfg, cfg,
sessionKey, sessionKey,

View File

@@ -23,6 +23,7 @@ import {
type SessionScope, type SessionScope,
saveSessionStore, saveSessionStore,
} from "../../config/sessions.js"; } from "../../config/sessions.js";
import { normalizeMainKey } from "../../routing/session-key.js";
import { resolveCommandAuthorization } from "../command-auth.js"; import { resolveCommandAuthorization } from "../command-auth.js";
import type { MsgContext, TemplateContext } from "../templating.js"; import type { MsgContext, TemplateContext } from "../templating.js";
import { stripMentions, stripStructuralPrefixes } from "./mentions.js"; import { stripMentions, stripStructuralPrefixes } from "./mentions.js";
@@ -90,7 +91,7 @@ export async function initSessionState(params: {
}): Promise<SessionInitResult> { }): Promise<SessionInitResult> {
const { ctx, cfg, commandAuthorized } = params; const { ctx, cfg, commandAuthorized } = params;
const sessionCfg = cfg.session; const sessionCfg = cfg.session;
const mainKey = sessionCfg?.mainKey ?? "main"; const mainKey = normalizeMainKey(sessionCfg?.mainKey);
const agentId = resolveAgentIdFromSessionKey(ctx.SessionKey); const agentId = resolveAgentIdFromSessionKey(ctx.SessionKey);
const resetTriggers = sessionCfg?.resetTriggers?.length const resetTriggers = sessionCfg?.resetTriggers?.length
? sessionCfg.resetTriggers ? sessionCfg.resetTriggers

View File

@@ -7,6 +7,7 @@ import {
resolveStorePath, resolveStorePath,
} from "../config/sessions.js"; } from "../config/sessions.js";
import { callGateway, randomIdempotencyKey } from "../gateway/call.js"; import { callGateway, randomIdempotencyKey } from "../gateway/call.js";
import { normalizeMainKey } from "../routing/session-key.js";
import type { RuntimeEnv } from "../runtime.js"; import type { RuntimeEnv } from "../runtime.js";
import { normalizeMessageProvider } from "../utils/message-provider.js"; import { normalizeMessageProvider } from "../utils/message-provider.js";
import { agentCommand } from "./agent.js"; import { agentCommand } from "./agent.js";
@@ -51,7 +52,7 @@ function resolveGatewaySessionKey(opts: {
}): string | undefined { }): string | undefined {
const sessionCfg = opts.cfg.session; const sessionCfg = opts.cfg.session;
const scope = sessionCfg?.scope ?? "per-sender"; const scope = sessionCfg?.scope ?? "per-sender";
const mainKey = sessionCfg?.mainKey ?? "main"; const mainKey = normalizeMainKey(sessionCfg?.mainKey);
const storePath = resolveStorePath(sessionCfg?.store); const storePath = resolveStorePath(sessionCfg?.store);
const store = loadSessionStore(storePath); const store = loadSessionStore(storePath);

View File

@@ -56,6 +56,7 @@ import {
normalizeOutboundPayloadsForJson, normalizeOutboundPayloadsForJson,
} from "../infra/outbound/payloads.js"; } from "../infra/outbound/payloads.js";
import { resolveOutboundTarget } from "../infra/outbound/targets.js"; import { resolveOutboundTarget } from "../infra/outbound/targets.js";
import { normalizeMainKey } from "../routing/session-key.js";
import { defaultRuntime, type RuntimeEnv } from "../runtime.js"; import { defaultRuntime, type RuntimeEnv } from "../runtime.js";
import { resolveSendPolicy } from "../sessions/send-policy.js"; import { resolveSendPolicy } from "../sessions/send-policy.js";
import { import {
@@ -104,7 +105,7 @@ function resolveSession(opts: {
}): SessionResolution { }): SessionResolution {
const sessionCfg = opts.cfg.session; const sessionCfg = opts.cfg.session;
const scope = sessionCfg?.scope ?? "per-sender"; const scope = sessionCfg?.scope ?? "per-sender";
const mainKey = sessionCfg?.mainKey ?? "main"; const mainKey = normalizeMainKey(sessionCfg?.mainKey);
const idleMinutes = Math.max( const idleMinutes = Math.max(
sessionCfg?.idleMinutes ?? DEFAULT_IDLE_MINUTES, sessionCfg?.idleMinutes ?? DEFAULT_IDLE_MINUTES,
1, 1,

View File

@@ -9,8 +9,8 @@ import type { MsgContext } from "../auto-reply/templating.js";
import { import {
buildAgentMainSessionKey, buildAgentMainSessionKey,
DEFAULT_AGENT_ID, DEFAULT_AGENT_ID,
DEFAULT_MAIN_KEY,
normalizeAgentId, normalizeAgentId,
normalizeMainKey,
resolveAgentIdFromSessionKey, resolveAgentIdFromSessionKey,
} from "../routing/session-key.js"; } from "../routing/session-key.js";
import { normalizeE164 } from "../utils.js"; import { normalizeE164 } from "../utils.js";
@@ -228,8 +228,7 @@ export function resolveMainSessionKey(cfg?: {
agents[0]?.id ?? agents[0]?.id ??
DEFAULT_AGENT_ID; DEFAULT_AGENT_ID;
const agentId = normalizeAgentId(defaultAgentId); const agentId = normalizeAgentId(defaultAgentId);
const mainKey = const mainKey = normalizeMainKey(cfg?.session?.mainKey);
(cfg?.session?.mainKey ?? DEFAULT_MAIN_KEY).trim() || DEFAULT_MAIN_KEY;
return buildAgentMainSessionKey({ agentId, mainKey }); return buildAgentMainSessionKey({ agentId, mainKey });
} }
@@ -243,9 +242,7 @@ export function resolveAgentMainSessionKey(params: {
cfg?: { session?: { mainKey?: string } }; cfg?: { session?: { mainKey?: string } };
agentId: string; agentId: string;
}): string { }): string {
const mainKey = const mainKey = normalizeMainKey(params.cfg?.session?.mainKey);
(params.cfg?.session?.mainKey ?? DEFAULT_MAIN_KEY).trim() ||
DEFAULT_MAIN_KEY;
return buildAgentMainSessionKey({ agentId: params.agentId, mainKey }); return buildAgentMainSessionKey({ agentId: params.agentId, mainKey });
} }
@@ -548,8 +545,7 @@ export function resolveSessionKey(
if (explicit) return explicit; if (explicit) return explicit;
const raw = deriveSessionKey(scope, ctx); const raw = deriveSessionKey(scope, ctx);
if (scope === "global") return raw; if (scope === "global") return raw;
const canonicalMainKey = const canonicalMainKey = normalizeMainKey(mainKey);
(mainKey ?? DEFAULT_MAIN_KEY).trim() || DEFAULT_MAIN_KEY;
const canonical = buildAgentMainSessionKey({ const canonical = buildAgentMainSessionKey({
agentId: DEFAULT_AGENT_ID, agentId: DEFAULT_AGENT_ID,
mainKey: canonicalMainKey, mainKey: canonicalMainKey,

View File

@@ -33,6 +33,7 @@ import {
setVoiceWakeTriggers, setVoiceWakeTriggers,
} from "../infra/voicewake.js"; } from "../infra/voicewake.js";
import { clearCommandLane } from "../process/command-queue.js"; import { clearCommandLane } from "../process/command-queue.js";
import { normalizeMainKey } from "../routing/session-key.js";
import { defaultRuntime } from "../runtime.js"; import { defaultRuntime } from "../runtime.js";
import { buildMessageWithAttachments } from "./chat-attachments.js"; import { buildMessageWithAttachments } from "./chat-attachments.js";
import { import {
@@ -944,8 +945,7 @@ 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 = const mainKey = normalizeMainKey(loadConfig().session?.mainKey);
(loadConfig().session?.mainKey ?? "main").trim() || "main";
const sessionKey = sessionKeyRaw.length > 0 ? sessionKeyRaw : mainKey; const sessionKey = sessionKeyRaw.length > 0 ? sessionKeyRaw : mainKey;
const { storePath, store, entry } = loadSessionEntry(sessionKey); const { storePath, store, entry } = loadSessionEntry(sessionKey);
const now = Date.now(); const now = Date.now();

View File

@@ -9,6 +9,7 @@ import {
saveSessionStore, saveSessionStore,
} from "../../config/sessions.js"; } from "../../config/sessions.js";
import { registerAgentRunContext } from "../../infra/agent-events.js"; import { registerAgentRunContext } from "../../infra/agent-events.js";
import { normalizeMainKey } from "../../routing/session-key.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";
import { normalizeMessageProvider } from "../../utils/message-provider.js"; import { normalizeMessageProvider } from "../../utils/message-provider.js";
@@ -129,7 +130,7 @@ export const agentHandlers: GatewayRequestHandlers = {
cfg, cfg,
agentId, agentId,
}); });
const rawMainKey = (cfg.session?.mainKey ?? "main").trim() || "main"; const rawMainKey = normalizeMainKey(cfg.session?.mainKey);
if ( if (
requestedSessionKey === mainSessionKey || requestedSessionKey === mainSessionKey ||
requestedSessionKey === rawMainKey requestedSessionKey === rawMainKey

View File

@@ -21,8 +21,8 @@ import {
type SessionScope, type SessionScope,
} from "../config/sessions.js"; } from "../config/sessions.js";
import { import {
DEFAULT_MAIN_KEY,
normalizeAgentId, normalizeAgentId,
normalizeMainKey,
parseAgentSessionKey, parseAgentSessionKey,
} from "../routing/session-key.js"; } from "../routing/session-key.js";
@@ -255,8 +255,7 @@ export function listAgentsForGateway(cfg: ClawdbotConfig): {
agents: GatewayAgentRow[]; agents: GatewayAgentRow[];
} { } {
const defaultId = normalizeAgentId(resolveDefaultAgentId(cfg)); const defaultId = normalizeAgentId(resolveDefaultAgentId(cfg));
const mainKey = const mainKey = normalizeMainKey(cfg.session?.mainKey);
(cfg.session?.mainKey ?? DEFAULT_MAIN_KEY).trim() || DEFAULT_MAIN_KEY;
const scope = cfg.session?.scope ?? "per-sender"; const scope = cfg.session?.scope ?? "per-sender";
const configuredById = new Map<string, { name?: string }>(); const configuredById = new Map<string, { name?: string }>();
for (const entry of cfg.agents?.list ?? []) { for (const entry of cfg.agents?.list ?? []) {

View File

@@ -6,6 +6,11 @@ function normalizeToken(value: string | undefined | null): string {
return (value ?? "").trim().toLowerCase(); return (value ?? "").trim().toLowerCase();
} }
export function normalizeMainKey(value: string | undefined | null): string {
const trimmed = (value ?? "").trim();
return trimmed ? trimmed : DEFAULT_MAIN_KEY;
}
export type ParsedAgentSessionKey = { export type ParsedAgentSessionKey = {
agentId: string; agentId: string;
rest: string; rest: string;
@@ -77,8 +82,7 @@ export function buildAgentMainSessionKey(params: {
mainKey?: string | undefined; mainKey?: string | undefined;
}): string { }): string {
const agentId = normalizeAgentId(params.agentId); const agentId = normalizeAgentId(params.agentId);
const mainKey = const mainKey = normalizeMainKey(params.mainKey);
(params.mainKey ?? DEFAULT_MAIN_KEY).trim() || DEFAULT_MAIN_KEY;
return `agent:${agentId}:${mainKey}`; return `agent:${agentId}:${mainKey}`;
} }

View File

@@ -53,7 +53,10 @@ import {
upsertProviderPairingRequest, upsertProviderPairingRequest,
} from "../pairing/pairing-store.js"; } from "../pairing/pairing-store.js";
import { resolveAgentRoute } from "../routing/resolve-route.js"; import { resolveAgentRoute } from "../routing/resolve-route.js";
import { resolveThreadSessionKeys } from "../routing/session-key.js"; import {
normalizeMainKey,
resolveThreadSessionKeys,
} from "../routing/session-key.js";
import type { RuntimeEnv } from "../runtime.js"; import type { RuntimeEnv } from "../runtime.js";
import { resolveSlackAccount } from "./accounts.js"; import { resolveSlackAccount } from "./accounts.js";
import { reactSlackMessage } from "./actions.js"; import { reactSlackMessage } from "./actions.js";
@@ -427,7 +430,7 @@ export async function monitorSlackProvider(opts: MonitorSlackOpts = {}) {
}); });
const sessionCfg = cfg.session; const sessionCfg = cfg.session;
const sessionScope = sessionCfg?.scope ?? "per-sender"; const sessionScope = sessionCfg?.scope ?? "per-sender";
const mainKey = (sessionCfg?.mainKey ?? "main").trim() || "main"; const mainKey = normalizeMainKey(sessionCfg?.mainKey);
const resolveSlackSystemEventSessionKey = (params: { const resolveSlackSystemEventSessionKey = (params: {
channelId?: string | null; channelId?: string | null;

View File

@@ -12,6 +12,7 @@ import { loadConfig } from "../config/config.js";
import { import {
buildAgentMainSessionKey, buildAgentMainSessionKey,
normalizeAgentId, normalizeAgentId,
normalizeMainKey,
parseAgentSessionKey, parseAgentSessionKey,
} from "../routing/session-key.js"; } from "../routing/session-key.js";
import { formatTokenCount } from "../utils/usage-format.js"; import { formatTokenCount } from "../utils/usage-format.js";
@@ -132,7 +133,7 @@ export async function runTui(opts: TuiOptions) {
const initialSessionInput = (opts.session ?? "").trim(); const initialSessionInput = (opts.session ?? "").trim();
let sessionScope: SessionScope = (config.session?.scope ?? let sessionScope: SessionScope = (config.session?.scope ??
"per-sender") as SessionScope; "per-sender") as SessionScope;
let sessionMainKey = (config.session?.mainKey ?? "main").trim() || "main"; let sessionMainKey = normalizeMainKey(config.session?.mainKey);
let agentDefaultId = resolveDefaultAgentId(config); let agentDefaultId = resolveDefaultAgentId(config);
let currentAgentId = agentDefaultId; let currentAgentId = agentDefaultId;
let agents: AgentSummary[] = []; let agents: AgentSummary[] = [];
@@ -263,7 +264,7 @@ export async function runTui(opts: TuiOptions) {
const applyAgentsResult = (result: GatewayAgentsList) => { const applyAgentsResult = (result: GatewayAgentsList) => {
agentDefaultId = normalizeAgentId(result.defaultId); agentDefaultId = normalizeAgentId(result.defaultId);
sessionMainKey = result.mainKey.trim() || sessionMainKey; sessionMainKey = normalizeMainKey(result.mainKey);
sessionScope = result.scope ?? sessionScope; sessionScope = result.scope ?? sessionScope;
agents = result.agents.map((agent) => ({ agents = result.agents.map((agent) => ({
id: normalizeAgentId(agent.id), id: normalizeAgentId(agent.id),

View File

@@ -57,6 +57,7 @@ import {
buildGroupHistoryKey, buildGroupHistoryKey,
DEFAULT_MAIN_KEY, DEFAULT_MAIN_KEY,
normalizeAgentId, normalizeAgentId,
normalizeMainKey,
} from "../routing/session-key.js"; } from "../routing/session-key.js";
import { defaultRuntime, type RuntimeEnv } from "../runtime.js"; import { defaultRuntime, type RuntimeEnv } from "../runtime.js";
import { isSelfChatMode, jidToE164, normalizeE164 } from "../utils.js"; import { isSelfChatMode, jidToE164, normalizeE164 } from "../utils.js";
@@ -291,7 +292,7 @@ export async function runWebHeartbeatOnce(opts: {
const cfg = cfgOverride ?? loadConfig(); const cfg = cfgOverride ?? loadConfig();
const sessionCfg = cfg.session; const sessionCfg = cfg.session;
const sessionScope = sessionCfg?.scope ?? "per-sender"; const sessionScope = sessionCfg?.scope ?? "per-sender";
const mainKey = sessionCfg?.mainKey; const mainKey = normalizeMainKey(sessionCfg?.mainKey);
const sessionKey = resolveSessionKey(sessionScope, { From: to }, mainKey); const sessionKey = resolveSessionKey(sessionScope, { From: to }, mainKey);
if (sessionId) { if (sessionId) {
const storePath = resolveStorePath(cfg.session?.store); const storePath = resolveStorePath(cfg.session?.store);
@@ -539,7 +540,7 @@ function getSessionSnapshot(
const key = resolveSessionKey( const key = resolveSessionKey(
scope, scope,
{ From: from, To: "", Body: "" }, { From: from, To: "", Body: "" },
sessionCfg?.mainKey, normalizeMainKey(sessionCfg?.mainKey),
); );
const store = loadSessionStore(resolveStorePath(sessionCfg?.store)); const store = loadSessionStore(resolveStorePath(sessionCfg?.store));
const entry = store[key]; const entry = store[key];