refactor: centralize session agent resolution

This commit is contained in:
Peter Steinberger
2026-01-10 01:57:33 +01:00
parent f4b3869f45
commit 623d1e11f1
10 changed files with 90 additions and 44 deletions

View File

@@ -3,7 +3,11 @@ import path from "node:path";
import type { ClawdbotConfig } from "../config/config.js";
import { resolveStateDir } from "../config/paths.js";
import { DEFAULT_AGENT_ID, normalizeAgentId } from "../routing/session-key.js";
import {
DEFAULT_AGENT_ID,
normalizeAgentId,
parseAgentSessionKey,
} from "../routing/session-key.js";
import { resolveUserPath } from "../utils.js";
import { DEFAULT_AGENT_WORKSPACE_DIR } from "./workspace.js";
@@ -49,6 +53,26 @@ export function resolveDefaultAgentId(cfg: ClawdbotConfig): string {
return normalizeAgentId(chosen || DEFAULT_AGENT_ID);
}
export function resolveSessionAgentIds(params: {
sessionKey?: string;
config?: ClawdbotConfig;
}): { defaultAgentId: string; sessionAgentId: string } {
const defaultAgentId = resolveDefaultAgentId(params.config ?? {});
const sessionKey = params.sessionKey?.trim();
const parsed = sessionKey ? parseAgentSessionKey(sessionKey) : null;
const sessionAgentId = parsed?.agentId
? normalizeAgentId(parsed.agentId)
: defaultAgentId;
return { defaultAgentId, sessionAgentId };
}
export function resolveSessionAgentId(params: {
sessionKey?: string;
config?: ClawdbotConfig;
}): string {
return resolveSessionAgentIds(params).sessionAgentId;
}
function resolveAgentEntry(
cfg: ClawdbotConfig,
agentId: string,

View File

@@ -9,6 +9,7 @@ import { shouldLogVerbose } from "../globals.js";
import { createSubsystemLogger } from "../logging.js";
import { runCommandWithTimeout } from "../process/exec.js";
import { resolveUserPath } from "../utils.js";
import { resolveSessionAgentIds } from "./agent-scope.js";
import { FailoverError, resolveFailoverStatus } from "./failover-error.js";
import {
buildBootstrapContextFiles,
@@ -136,6 +137,7 @@ function buildSystemPrompt(params: {
defaultThinkLevel?: ThinkLevel;
extraSystemPrompt?: string;
ownerNumbers?: string[];
heartbeatPrompt?: string;
tools: AgentTool[];
contextFiles?: EmbeddedContextFile[];
modelDisplay: string;
@@ -150,9 +152,7 @@ function buildSystemPrompt(params: {
extraSystemPrompt: params.extraSystemPrompt,
ownerNumbers: params.ownerNumbers,
reasoningTagHint: false,
heartbeatPrompt: resolveHeartbeatPrompt(
params.config?.agents?.defaults?.heartbeat?.prompt,
),
heartbeatPrompt: params.heartbeatPrompt,
runtimeInfo: {
host: "clawdbot",
os: `${os.type()} ${os.release()}`,
@@ -374,12 +374,23 @@ export async function runClaudeCliAgent(params: {
params.sessionKey ?? params.sessionId,
);
const contextFiles = buildBootstrapContextFiles(bootstrapFiles);
const { defaultAgentId, sessionAgentId } = resolveSessionAgentIds({
sessionKey: params.sessionKey,
config: params.config,
});
const heartbeatPrompt =
sessionAgentId === defaultAgentId
? resolveHeartbeatPrompt(
params.config?.agents?.defaults?.heartbeat?.prompt,
)
: undefined;
const systemPrompt = buildSystemPrompt({
workspaceDir,
config: params.config,
defaultThinkLevel: params.thinkLevel,
extraSystemPrompt,
ownerNumbers: params.ownerNumbers,
heartbeatPrompt,
tools: [],
contextFiles,
modelDisplay,

View File

@@ -3,11 +3,11 @@ import { SessionManager } from "@mariozechner/pi-coding-agent";
import { Type } from "@sinclair/typebox";
import { describe, expect, it, vi } from "vitest";
import type { ClawdbotConfig } from "../config/config.js";
import { resolveSessionAgentIds } from "./agent-scope.js";
import {
applyGoogleTurnOrderingFix,
buildEmbeddedSandboxInfo,
createSystemPromptOverride,
resolveSessionAgentIds,
splitSdkTools,
} from "./pi-embedded-runner.js";
import type { SandboxContext } from "./sandbox.js";
@@ -82,6 +82,22 @@ describe("resolveSessionAgentIds", () => {
expect(sessionAgentId).toBe("beta");
});
it("falls back to the configured default for global sessions", () => {
const { sessionAgentId } = resolveSessionAgentIds({
sessionKey: "global",
config: cfg,
});
expect(sessionAgentId).toBe("beta");
});
it("keeps the agent id for provider-qualified agent sessions", () => {
const { sessionAgentId } = resolveSessionAgentIds({
sessionKey: "agent:beta:slack:channel:C1",
config: cfg,
});
expect(sessionAgentId).toBe("beta");
});
it("uses the agent id from agent session keys", () => {
const { sessionAgentId } = resolveSessionAgentIds({
sessionKey: "agent:main:main",

View File

@@ -33,14 +33,10 @@ import {
type enqueueCommand,
enqueueCommandInLane,
} from "../process/command-queue.js";
import {
normalizeAgentId,
parseAgentSessionKey,
} from "../routing/session-key.js";
import { normalizeMessageProvider } from "../utils/message-provider.js";
import { resolveUserPath } from "../utils.js";
import { resolveClawdbotAgentDir } from "./agent-paths.js";
import { resolveDefaultAgentId } from "./agent-scope.js";
import { resolveSessionAgentIds } from "./agent-scope.js";
import {
markAuthProfileFailure,
markAuthProfileGood,
@@ -560,19 +556,6 @@ export function buildEmbeddedSandboxInfo(
};
}
export function resolveSessionAgentIds(params: {
sessionKey?: string;
config?: ClawdbotConfig;
}): { defaultAgentId: string; sessionAgentId: string } {
const defaultAgentId = resolveDefaultAgentId(params.config ?? {});
const sessionKey = params.sessionKey?.trim();
const parsed = sessionKey ? parseAgentSessionKey(sessionKey) : null;
const sessionAgentId = parsed?.agentId
? normalizeAgentId(parsed.agentId)
: defaultAgentId;
return { defaultAgentId, sessionAgentId };
}
function buildEmbeddedSystemPrompt(params: {
workspaceDir: string;
defaultThinkLevel?: ThinkLevel;

View File

@@ -5,8 +5,8 @@ import { fileURLToPath } from "node:url";
import {
resolveAgentConfig,
resolveAgentDir,
resolveAgentIdFromSessionKey,
resolveAgentWorkspaceDir,
resolveSessionAgentId,
} from "../agents/agent-scope.js";
import { resolveModelRefFromString } from "../agents/model-selection.js";
import {
@@ -250,7 +250,10 @@ export async function getReplyFromConfig(
configOverride?: ClawdbotConfig,
): Promise<ReplyPayload | ReplyPayload[] | undefined> {
const cfg = configOverride ?? loadConfig();
const agentId = resolveAgentIdFromSessionKey(ctx.SessionKey);
const agentId = resolveSessionAgentId({
sessionKey: ctx.SessionKey,
config: cfg,
});
const agentCfg = cfg.agents?.defaults;
const sessionCfg = cfg.session;
const { defaultProvider, defaultModel, aliasIndex } = resolveDefaultModel({

View File

@@ -1,3 +1,4 @@
import { resolveSessionAgentId } from "../../agents/agent-scope.js";
import { abortEmbeddedPiRun } from "../../agents/pi-embedded.js";
import type { ClawdbotConfig } from "../../config/config.js";
import {
@@ -7,10 +8,7 @@ import {
saveSessionStore,
type SessionEntry,
} from "../../config/sessions.js";
import {
parseAgentSessionKey,
resolveAgentIdFromSessionKey,
} from "../../routing/session-key.js";
import { parseAgentSessionKey } from "../../routing/session-key.js";
import { resolveCommandAuthorization } from "../command-auth.js";
import {
normalizeCommandBody,
@@ -80,9 +78,10 @@ export async function tryFastAbortFromMessage(params: {
if (!auth.isAuthorizedSender) return { handled: false, aborted: false };
const targetKey = resolveAbortTargetKey(ctx);
const agentId = resolveAgentIdFromSessionKey(
targetKey ?? ctx.SessionKey ?? "",
);
const agentId = resolveSessionAgentId({
sessionKey: targetKey ?? ctx.SessionKey ?? "",
config: cfg,
});
const raw = stripStructuralPrefixes(ctx.Body ?? "");
const isGroup = ctx.ChatType?.trim().toLowerCase() === "group";
const stripped = isGroup ? stripMentions(raw, ctx, cfg, agentId) : raw;

View File

@@ -1,6 +1,7 @@
import {
resolveAgentDir,
resolveDefaultAgentId,
resolveSessionAgentId,
} from "../../agents/agent-scope.js";
import {
ensureAuthProfileStore,
@@ -26,7 +27,6 @@ import {
unsetConfigOverride,
} from "../../config/runtime-overrides.js";
import {
resolveAgentIdFromSessionKey,
resolveSessionFilePath,
type SessionEntry,
type SessionScope,
@@ -148,7 +148,7 @@ export async function buildStatusReply(params: {
return undefined;
}
const statusAgentId = sessionKey
? resolveAgentIdFromSessionKey(sessionKey)
? resolveSessionAgentId({ sessionKey, config: cfg })
: resolveDefaultAgentId(cfg);
const statusAgentDir = resolveAgentDir(cfg, statusAgentId);
let usageLine: string | null = null;

View File

@@ -2,6 +2,7 @@ import {
resolveAgentConfig,
resolveAgentDir,
resolveDefaultAgentId,
resolveSessionAgentId,
} from "../../agents/agent-scope.js";
import {
isProfileInCooldown,
@@ -31,7 +32,6 @@ import {
import { resolveSandboxConfigForAgent } from "../../agents/sandbox.js";
import type { ClawdbotConfig } from "../../config/config.js";
import {
resolveAgentIdFromSessionKey,
resolveAgentMainSessionKey,
type SessionEntry,
saveSessionStore,
@@ -491,14 +491,18 @@ export async function handleDirectiveOnly(params: {
currentReasoningLevel,
currentElevatedLevel,
} = params;
const activeAgentId = params.sessionKey
? resolveAgentIdFromSessionKey(params.sessionKey)
: resolveDefaultAgentId(params.cfg);
const activeAgentId = resolveSessionAgentId({
sessionKey: params.sessionKey,
config: params.cfg,
});
const agentDir = resolveAgentDir(params.cfg, activeAgentId);
const runtimeIsSandboxed = (() => {
const sessionKey = params.sessionKey?.trim();
if (!sessionKey) return false;
const agentId = resolveAgentIdFromSessionKey(sessionKey);
const agentId = resolveSessionAgentId({
sessionKey,
config: params.cfg,
});
const sandboxCfg = resolveSandboxConfigForAgent(params.cfg, agentId);
if (sandboxCfg.mode === "off") return false;
const mainKey = resolveAgentMainSessionKey({
@@ -1013,7 +1017,7 @@ export async function persistInlineDirectives(params: {
} = params;
let { provider, model } = params;
const activeAgentId = sessionKey
? resolveAgentIdFromSessionKey(sessionKey)
? resolveSessionAgentId({ sessionKey, config: cfg })
: resolveDefaultAgentId(cfg);
const agentDir = resolveAgentDir(cfg, activeAgentId);

View File

@@ -7,12 +7,12 @@
* across multiple providers.
*/
import { resolveSessionAgentId } from "../../agents/agent-scope.js";
import { resolveEffectiveMessagesConfig } from "../../agents/identity.js";
import type { ClawdbotConfig } from "../../config/config.js";
import { sendMessageDiscord } from "../../discord/send.js";
import { sendMessageIMessage } from "../../imessage/send.js";
import { sendMessageMSTeams } from "../../msteams/send.js";
import { resolveAgentIdFromSessionKey } from "../../routing/session-key.js";
import { sendMessageSignal } from "../../signal/send.js";
import { sendMessageSlack } from "../../slack/send.js";
import { sendMessageTelegram } from "../../telegram/send.js";
@@ -67,7 +67,10 @@ export async function routeReply(
const responsePrefix = params.sessionKey
? resolveEffectiveMessagesConfig(
cfg,
resolveAgentIdFromSessionKey(params.sessionKey),
resolveSessionAgentId({
sessionKey: params.sessionKey,
config: cfg,
}),
).responsePrefix
: cfg.messages?.responsePrefix === "auto"
? undefined

View File

@@ -6,6 +6,7 @@ import {
CURRENT_SESSION_VERSION,
SessionManager,
} from "@mariozechner/pi-coding-agent";
import { resolveSessionAgentId } from "../../agents/agent-scope.js";
import type { ClawdbotConfig } from "../../config/config.js";
import {
buildGroupDisplayName,
@@ -13,7 +14,6 @@ import {
DEFAULT_RESET_TRIGGERS,
type GroupKeyResolution,
loadSessionStore,
resolveAgentIdFromSessionKey,
resolveGroupSessionKey,
resolveSessionFilePath,
resolveSessionKey,
@@ -92,7 +92,10 @@ export async function initSessionState(params: {
const { ctx, cfg, commandAuthorized } = params;
const sessionCfg = cfg.session;
const mainKey = normalizeMainKey(sessionCfg?.mainKey);
const agentId = resolveAgentIdFromSessionKey(ctx.SessionKey);
const agentId = resolveSessionAgentId({
sessionKey: ctx.SessionKey,
config: cfg,
});
const resetTriggers = sessionCfg?.resetTriggers?.length
? sessionCfg.resetTriggers
: DEFAULT_RESET_TRIGGERS;