refactor: drop legacy session store keys

This commit is contained in:
Peter Steinberger
2026-01-17 06:48:34 +00:00
parent 353d778988
commit 37a2eee837
22 changed files with 65 additions and 93 deletions

View File

@@ -136,11 +136,7 @@ function loadRequesterSessionEntry(requesterSessionKey: string) {
const agentId = resolveAgentIdFromSessionKey(canonicalKey);
const storePath = resolveStorePath(cfg.session?.store, { agentId });
const store = loadSessionStore(storePath);
const legacyKey = canonicalKey.startsWith("agent:")
? canonicalKey.split(":").slice(2).join(":")
: undefined;
const entry =
store[canonicalKey] ?? store[requesterSessionKey] ?? (legacyKey ? store[legacyKey] : undefined);
const entry = store[canonicalKey];
return { cfg, entry, canonicalKey };
}

View File

@@ -52,11 +52,6 @@ function resolveSessionEntryForKey(
if (!store || !sessionKey) return {};
const direct = store[sessionKey];
if (direct) return { entry: direct, key: sessionKey };
const parsed = parseAgentSessionKey(sessionKey);
const legacyKey = parsed?.rest;
if (legacyKey && store[legacyKey]) {
return { entry: store[legacyKey], key: legacyKey };
}
return {};
}

View File

@@ -4,7 +4,6 @@ import { updateSessionStore } from "../../config/sessions.js";
import { logVerbose } from "../../globals.js";
import { createInternalHookEvent, triggerInternalHook } from "../../hooks/internal-hooks.js";
import { scheduleGatewaySigusr1Restart, triggerClawdbotRestart } from "../../infra/restart.js";
import { parseAgentSessionKey } from "../../routing/session-key.js";
import { parseActivationCommand } from "../group-activation.js";
import { parseSendPolicyCommand } from "../send-policy.js";
import {
@@ -23,11 +22,6 @@ function resolveSessionEntryForKey(
if (!store || !sessionKey) return {};
const direct = store[sessionKey];
if (direct) return { entry: direct, key: sessionKey };
const parsed = parseAgentSessionKey(sessionKey);
const legacyKey = parsed?.rest;
if (legacyKey && store[legacyKey]) {
return { entry: store[legacyKey], key: legacyKey };
}
return {};
}

View File

@@ -1,6 +1,7 @@
import { normalizeChatType } from "../../channels/chat-type.js";
import { resolveConversationLabel } from "../../channels/conversation-label.js";
import type { MsgContext } from "../templating.js";
import { formatInboundBodyWithSenderMeta } from "./inbound-sender-meta.js";
import { normalizeInboundTextNewlines } from "./inbound-text.js";
export type FinalizeInboundContextOptions = {
@@ -55,5 +56,13 @@ export function finalizeInboundContext<T extends Record<string, unknown>>(
normalized.ConversationLabel = explicitLabel;
}
// Ensure group/channel messages retain a sender meta line even when the body is a
// structured envelope (e.g. "[Signal ...] Alice: hi").
normalized.Body = formatInboundBodyWithSenderMeta({ ctx: normalized, body: normalized.Body });
normalized.BodyForAgent = formatInboundBodyWithSenderMeta({
ctx: normalized,
body: normalized.BodyForAgent,
});
return normalized;
}

View File

@@ -170,13 +170,6 @@ export async function initSessionState(params: {
}
sessionKey = resolveSessionKey(sessionScope, sessionCtxForState, mainKey);
if (groupResolution?.legacyKey && groupResolution.legacyKey !== sessionKey) {
const legacyEntry = sessionStore[groupResolution.legacyKey];
if (legacyEntry && !sessionStore[sessionKey]) {
sessionStore[sessionKey] = legacyEntry;
delete sessionStore[groupResolution.legacyKey];
}
}
const entry = sessionStore[sessionKey];
const previousSessionEntry = resetTriggered && entry ? { ...entry } : undefined;
const idleMs = idleMinutes * 60_000;
@@ -309,12 +302,6 @@ export async function initSessionState(params: {
// Preserve per-session overrides while resetting compaction state on /new.
sessionStore[sessionKey] = { ...sessionStore[sessionKey], ...sessionEntry };
await updateSessionStore(storePath, (store) => {
if (groupResolution?.legacyKey && groupResolution.legacyKey !== sessionKey) {
if (store[groupResolution.legacyKey] && !store[sessionKey]) {
store[sessionKey] = store[groupResolution.legacyKey];
}
delete store[groupResolution.legacyKey];
}
// Preserve per-session overrides while resetting compaction state on /new.
store[sessionKey] = { ...store[sessionKey], ...sessionEntry };
});

View File

@@ -4,6 +4,7 @@ import type { MsgContext } from "../../auto-reply/templating.js";
import type { ClawdbotConfig } from "../../config/config.js";
import type { PollInput } from "../../polls.js";
import type { GatewayClientMode, GatewayClientName } from "../../utils/message-channel.js";
import type { NormalizedChatType } from "../chat-type.js";
import type { ChatChannelId } from "../registry.js";
import type { ChannelMessageActionName as ChannelMessageActionNameFromList } from "./message-action-names.js";
@@ -138,7 +139,7 @@ export type ChannelGroupContext = {
};
export type ChannelCapabilities = {
chatTypes: Array<"direct" | "group" | "channel" | "thread">;
chatTypes: Array<NormalizedChatType | "thread">;
polls?: boolean;
reactions?: boolean;
threads?: boolean;

View File

@@ -63,8 +63,8 @@ export function resolveGroupSessionKey(ctx: MsgContext): GroupKeyResolution | nu
if (!isGroup) return null;
const providerHint = ctx.Provider?.trim().toLowerCase();
const hasLegacyGroupPrefix = from.startsWith("group:");
const raw = (hasLegacyGroupPrefix ? from.slice("group:".length) : from).trim();
const hasGroupPrefix = from.startsWith("group:");
const raw = (hasGroupPrefix ? from.slice("group:".length) : from).trim();
let provider: string | undefined;
let kind: "group" | "channel" | undefined;
@@ -97,7 +97,7 @@ export function resolveGroupSessionKey(ctx: MsgContext): GroupKeyResolution | nu
}
};
if (hasLegacyGroupPrefix) {
if (hasGroupPrefix) {
const legacyParts = raw.split(":").filter(Boolean);
if (legacyParts.length > 1) {
parseParts(legacyParts);
@@ -115,25 +115,19 @@ export function resolveGroupSessionKey(ctx: MsgContext): GroupKeyResolution | nu
const resolvedProvider = provider ?? providerHint;
if (!resolvedProvider) {
const legacy = hasLegacyGroupPrefix ? `group:${raw}` : `group:${from}`;
const legacy = hasGroupPrefix ? `group:${raw}` : `group:${from}`;
return {
key: legacy,
id: raw || from,
legacyKey: legacy,
chatType: "group",
};
}
const resolvedKind = kind === "channel" ? "channel" : "group";
const key = `${resolvedProvider}:${resolvedKind}:${id || raw || from}`;
let legacyKey: string | undefined;
if (hasLegacyGroupPrefix || from.includes("@g.us")) {
legacyKey = `group:${id || raw || from}`;
}
return {
key,
legacyKey,
channel: resolvedProvider,
id: id || raw || from,
chatType: resolvedKind === "channel" ? "channel" : "group",

View File

@@ -1,6 +1,7 @@
import crypto from "node:crypto";
import type { Skill } from "@mariozechner/pi-coding-agent";
import type { NormalizedChatType } from "../../channels/chat-type.js";
import type { ChannelId } from "../../channels/plugins/types.js";
import type { DeliveryContext } from "../../utils/delivery-context.js";
@@ -8,10 +9,7 @@ export type SessionScope = "per-sender" | "global";
export type SessionChannelId = ChannelId | "webchat";
export type SessionChatType =
| "direct"
| "group"
| "channel";
export type SessionChatType = NormalizedChatType;
export type SessionEntry = {
/**
@@ -90,7 +88,6 @@ export function mergeSessionEntry(
export type GroupKeyResolution = {
key: string;
legacyKey?: string;
channel?: string;
id?: string;
chatType?: SessionChatType;

View File

@@ -1,3 +1,5 @@
import type { NormalizedChatType } from "../channels/chat-type.js";
export type ReplyMode = "text" | "command";
export type TypingMode = "never" | "instant" | "thinking" | "message";
export type SessionScope = "per-sender" | "global";
@@ -41,7 +43,7 @@ export type HumanDelayConfig = {
export type SessionSendPolicyAction = "allow" | "deny";
export type SessionSendPolicyMatch = {
channel?: string;
chatType?: "direct" | "group" | "channel";
chatType?: NormalizedChatType;
keyPrefix?: string;
};
export type SessionSendPolicyRule = {

View File

@@ -1,8 +1,9 @@
import type { NormalizedChatType } from "../channels/chat-type.js";
import type { AgentElevatedAllowFromConfig, SessionSendPolicyAction } from "./types.base.js";
export type MediaUnderstandingScopeMatch = {
channel?: string;
chatType?: "direct" | "group" | "channel";
chatType?: NormalizedChatType;
keyPrefix?: string;
};

View File

@@ -1,6 +1,7 @@
import { loadConfig } from "../config/config.js";
import { loadSessionStore, resolveStorePath } from "../config/sessions.js";
import { getAgentRunContext, registerAgentRunContext } from "../infra/agent-events.js";
import { parseAgentSessionKey } from "../routing/session-key.js";
export function resolveSessionKeyForRun(runId: string) {
const cached = getAgentRunContext(runId)?.sessionKey;
@@ -9,9 +10,12 @@ export function resolveSessionKeyForRun(runId: string) {
const storePath = resolveStorePath(cfg.session?.store);
const store = loadSessionStore(storePath);
const found = Object.entries(store).find(([, entry]) => entry?.sessionId === runId);
const sessionKey = found?.[0];
if (sessionKey) {
const storeKey = found?.[0];
if (storeKey) {
const parsed = parseAgentSessionKey(storeKey);
const sessionKey = parsed?.rest ?? storeKey;
registerAgentRunContext(runId, { sessionKey });
return sessionKey;
}
return sessionKey;
return undefined;
}

View File

@@ -30,7 +30,7 @@ describe("gateway server agent", () => {
testState.sessionStorePath,
JSON.stringify(
{
main: {
"agent:main:main": {
sessionId: "sess-main-stale",
updatedAt: Date.now(),
lastChannel: "whatsapp",
@@ -115,7 +115,7 @@ describe("gateway server agent", () => {
testState.sessionStorePath,
JSON.stringify(
{
main: {
"agent:main:main": {
sessionId: "sess-main-account",
updatedAt: Date.now(),
lastChannel: "whatsapp",
@@ -160,7 +160,7 @@ describe("gateway server agent", () => {
testState.sessionStorePath,
JSON.stringify(
{
main: {
"agent:main:main": {
sessionId: "sess-main-explicit",
updatedAt: Date.now(),
lastChannel: "whatsapp",
@@ -205,7 +205,7 @@ describe("gateway server agent", () => {
testState.sessionStorePath,
JSON.stringify(
{
main: {
"agent:main:main": {
sessionId: "sess-main-explicit-account",
updatedAt: Date.now(),
lastChannel: "whatsapp",
@@ -251,7 +251,7 @@ describe("gateway server agent", () => {
testState.sessionStorePath,
JSON.stringify(
{
main: {
"agent:main:main": {
sessionId: "sess-main-implicit",
updatedAt: Date.now(),
lastChannel: "whatsapp",
@@ -294,7 +294,7 @@ describe("gateway server agent", () => {
testState.sessionStorePath,
JSON.stringify(
{
main: {
"agent:main:main": {
sessionId: "sess-main-images",
updatedAt: Date.now(),
},
@@ -347,7 +347,7 @@ describe("gateway server agent", () => {
testState.sessionStorePath,
JSON.stringify(
{
main: {
"agent:main:main": {
sessionId: "sess-main-missing-provider",
updatedAt: Date.now(),
},
@@ -388,7 +388,7 @@ describe("gateway server agent", () => {
testState.sessionStorePath,
JSON.stringify(
{
main: {
"agent:main:main": {
sessionId: "sess-main-whatsapp",
updatedAt: Date.now(),
lastChannel: "whatsapp",
@@ -433,7 +433,7 @@ describe("gateway server agent", () => {
testState.sessionStorePath,
JSON.stringify(
{
main: {
"agent:main:main": {
sessionId: "sess-main",
updatedAt: Date.now(),
lastChannel: "telegram",
@@ -477,7 +477,7 @@ describe("gateway server agent", () => {
testState.sessionStorePath,
JSON.stringify(
{
main: {
"agent:main:main": {
sessionId: "sess-discord",
updatedAt: Date.now(),
lastChannel: "discord",
@@ -521,7 +521,7 @@ describe("gateway server agent", () => {
testState.sessionStorePath,
JSON.stringify(
{
main: {
"agent:main:main": {
sessionId: "sess-slack",
updatedAt: Date.now(),
lastChannel: "slack",
@@ -565,7 +565,7 @@ describe("gateway server agent", () => {
testState.sessionStorePath,
JSON.stringify(
{
main: {
"agent:main:main": {
sessionId: "sess-signal",
updatedAt: Date.now(),
lastChannel: "signal",

View File

@@ -84,7 +84,7 @@ describe("gateway server agent", () => {
testState.sessionStorePath,
JSON.stringify(
{
main: {
"agent:main:main": {
sessionId: "sess-teams",
updatedAt: Date.now(),
lastChannel: "msteams",
@@ -137,7 +137,7 @@ describe("gateway server agent", () => {
testState.sessionStorePath,
JSON.stringify(
{
main: {
"agent:main:main": {
sessionId: "sess-alias",
updatedAt: Date.now(),
lastChannel: "imessage",
@@ -210,7 +210,7 @@ describe("gateway server agent", () => {
testState.sessionStorePath,
JSON.stringify(
{
main: {
"agent:main:main": {
sessionId: "sess-main-webchat",
updatedAt: Date.now(),
lastChannel: "webchat",
@@ -254,7 +254,7 @@ describe("gateway server agent", () => {
testState.sessionStorePath,
JSON.stringify(
{
main: {
"agent:main:main": {
sessionId: "sess-main-webchat-internal",
updatedAt: Date.now(),
lastChannel: "webchat",
@@ -412,7 +412,7 @@ describe("gateway server agent", () => {
testState.sessionStorePath,
JSON.stringify(
{
main: {
"agent:main:main": {
sessionId: "sess-main",
updatedAt: Date.now(),
},

View File

@@ -240,7 +240,7 @@ describe("gateway server chat", () => {
testState.sessionStorePath,
JSON.stringify(
{
main: {
"agent:main:main": {
sessionId: "sess-main",
updatedAt: Date.now(),
},
@@ -353,7 +353,7 @@ describe("gateway server chat", () => {
testState.sessionStorePath,
JSON.stringify(
{
main: {
"agent:main:main": {
sessionId: "sess-main",
sessionFile: forkedPath,
updatedAt: Date.now(),
@@ -401,7 +401,7 @@ describe("gateway server chat", () => {
testState.sessionStorePath,
JSON.stringify(
{
main: {
"agent:main:main": {
sessionId: "sess-main",
updatedAt: Date.now(),
},
@@ -451,7 +451,7 @@ describe("gateway server chat", () => {
testState.sessionStorePath,
JSON.stringify(
{
main: {
"agent:main:main": {
sessionId: "sess-main",
updatedAt: Date.now(),
},

View File

@@ -221,7 +221,7 @@ describe("gateway server node/bridge", () => {
testState.sessionStorePath,
JSON.stringify(
{
main: {
"agent:main:main": {
sessionId: "sess-main",
updatedAt: Date.now(),
},
@@ -278,7 +278,7 @@ describe("gateway server node/bridge", () => {
testState.sessionStorePath,
JSON.stringify(
{
main: {
"agent:main:main": {
sessionId: "sess-main",
updatedAt: Date.now(),
},
@@ -335,7 +335,7 @@ describe("gateway server node/bridge", () => {
testState.sessionStorePath,
JSON.stringify(
{
main: {
"agent:main:main": {
sessionId: "sess-main",
updatedAt: Date.now(),
},
@@ -412,7 +412,7 @@ describe("gateway server node/bridge", () => {
testState.sessionStorePath,
JSON.stringify(
{
main: {
"agent:main:main": {
sessionId: "sess-main",
updatedAt: Date.now(),
},

View File

@@ -46,7 +46,7 @@ describe("gateway server node/bridge", () => {
testState.sessionStorePath,
JSON.stringify(
{
main: {
"agent:main:main": {
sessionId: "sess-main",
updatedAt: Date.now(),
lastChannel: "whatsapp",
@@ -83,7 +83,7 @@ describe("gateway server node/bridge", () => {
string,
{ sessionId?: string } | undefined
>;
expect(stored.main?.sessionId).toBe("sess-main");
expect(stored["agent:main:main"]?.sessionId).toBe("sess-main");
expect(stored["node-ios-node"]).toBeUndefined();
await server.close();
@@ -96,7 +96,7 @@ describe("gateway server node/bridge", () => {
testState.sessionStorePath,
JSON.stringify(
{
main: {
"agent:main:main": {
sessionId: "sess-main",
updatedAt: Date.now(),
},
@@ -187,7 +187,7 @@ describe("gateway server node/bridge", () => {
testState.sessionStorePath,
JSON.stringify(
{
main: {
"agent:main:main": {
sessionId: "sess-main",
updatedAt: Date.now(),
},

View File

@@ -359,8 +359,8 @@ describe("gateway server sessions", () => {
storePath,
JSON.stringify(
{
main: { sessionId: "sess-main", updatedAt: Date.now() },
"discord:group:dev": {
"agent:main:main": { sessionId: "sess-main", updatedAt: Date.now() },
"agent:main:discord:group:dev": {
sessionId: "sess-active",
updatedAt: Date.now(),
},

View File

@@ -50,10 +50,7 @@ export function loadSessionEntry(sessionKey: string) {
const agentId = resolveSessionStoreAgentId(cfg, canonicalKey);
const storePath = resolveStorePath(sessionCfg?.store, { agentId });
const store = loadSessionStore(storePath);
const parsed = parseAgentSessionKey(canonicalKey);
const legacyKey = parsed?.rest ?? parseAgentSessionKey(sessionKey)?.rest ?? undefined;
const entry =
store[canonicalKey] ?? store[sessionKey] ?? (legacyKey ? store[legacyKey] : undefined);
const entry = store[canonicalKey];
return { cfg, storePath, store, entry, canonicalKey };
}
@@ -248,10 +245,8 @@ export function resolveGatewaySessionStoreTarget(params: { cfg: ClawdbotConfig;
return { agentId, storePath, canonicalKey, storeKeys };
}
const parsed = parseAgentSessionKey(canonicalKey);
const storeKeys = new Set<string>();
storeKeys.add(canonicalKey);
if (parsed?.rest) storeKeys.add(parsed.rest);
if (key && key !== canonicalKey) storeKeys.add(key);
return {
agentId,

View File

@@ -1,3 +1,4 @@
import type { NormalizedChatType } from "../channels/chat-type.js";
import type { SessionEntry } from "../config/sessions.js";
import type { DeliveryContext } from "../utils/delivery-context.js";
@@ -16,7 +17,7 @@ export type GatewaySessionRow = {
subject?: string;
room?: string;
space?: string;
chatType?: "direct" | "group" | "channel";
chatType?: NormalizedChatType;
updatedAt: number | null;
sessionId?: string;
systemSent?: boolean;

View File

@@ -27,7 +27,6 @@ import {
} from "../../auto-reply/reply/history.js";
import { buildMentionRegexes, matchesMentionPatterns } from "../../auto-reply/reply/mentions.js";
import { createReplyDispatcher } from "../../auto-reply/reply/reply-dispatcher.js";
import { formatInboundBodyWithSenderMeta } from "../../auto-reply/reply/inbound-sender-meta.js";
import { loadConfig } from "../../config/config.js";
import {
resolveChannelGroupPolicy,
@@ -419,7 +418,6 @@ export async function monitorIMessageProvider(opts: MonitorIMessageOpts = {}): P
OriginatingChannel: "imessage" as const,
OriginatingTo: imessageTo,
});
ctxPayload.Body = formatInboundBodyWithSenderMeta({ ctx: ctxPayload, body: ctxPayload.Body });
if (!isGroup) {
const sessionCfg = cfg.session;

View File

@@ -19,7 +19,6 @@ import {
clearHistoryEntries,
} from "../../auto-reply/reply/history.js";
import { finalizeInboundContext } from "../../auto-reply/reply/inbound-context.js";
import { formatInboundBodyWithSenderMeta } from "../../auto-reply/reply/inbound-sender-meta.js";
import { createReplyDispatcher } from "../../auto-reply/reply/reply-dispatcher.js";
import { resolveStorePath, updateLastRoute } from "../../config/sessions.js";
import { danger, logVerbose, shouldLogVerbose } from "../../globals.js";
@@ -134,7 +133,6 @@ export function createSignalEventHandler(deps: SignalEventHandlerDeps) {
OriginatingChannel: "signal" as const,
OriginatingTo: signalTo,
});
ctxPayload.Body = formatInboundBodyWithSenderMeta({ ctx: ctxPayload, body: ctxPayload.Body });
if (!entry.isGroup) {
const sessionCfg = deps.cfg.session;

View File

@@ -274,7 +274,7 @@ export async function prepareSlackMessage(params: {
});
const effectiveWasMentioned = mentionGate.effectiveWasMentioned;
if (isRoom && shouldRequireMention && mentionGate.shouldSkip) {
ctx.logger.info({ channel: message.channel, reason: "no-mention" }, "skipping room message");
ctx.logger.info({ channel: message.channel, reason: "no-mention" }, "skipping channel message");
if (ctx.historyLimit > 0) {
const pendingText = (message.text ?? "").trim();
const fallbackFile = message.files?.[0]?.name