refactor: require session state for directive handling

This commit is contained in:
Peter Steinberger
2026-01-22 22:11:12 +00:00
parent c0c8ee217f
commit da3a141c58
6 changed files with 104 additions and 206 deletions

View File

@@ -2,7 +2,7 @@
Docs: https://docs.clawd.bot
## 2026.1.22
## 2026.1.22 (unreleased)
### Fixes
- BlueBubbles: stop typing indicator on idle/no-reply. (#1439) Thanks @Nicell.

View File

@@ -15,8 +15,8 @@ export async function applyInlineDirectivesFastLane(params: {
cfg: ClawdbotConfig;
agentId?: string;
isGroup: boolean;
sessionEntry?: SessionEntry;
sessionStore?: Record<string, SessionEntry>;
sessionEntry: SessionEntry;
sessionStore: Record<string, SessionEntry>;
sessionKey: string;
storePath?: string;
elevatedEnabled: boolean;

View File

@@ -77,100 +77,6 @@ describe("handleDirectiveOnly model persist behavior (fixes #1435)", () => {
expect(result?.text).not.toContain("failed");
});
it("shows error message when sessionEntry is missing", async () => {
const directives = parseInlineDirectives("/model openai/gpt-4o");
const sessionStore = {};
const result = await handleDirectiveOnly({
cfg: baseConfig(),
directives,
sessionEntry: undefined, // Missing!
sessionStore,
sessionKey: "agent:main:dm:1",
storePath: "/tmp/sessions.json",
elevatedEnabled: false,
elevatedAllowed: false,
defaultProvider: "anthropic",
defaultModel: "claude-opus-4-5",
aliasIndex: baseAliasIndex(),
allowedModelKeys,
allowedModelCatalog,
resetModelOverride: false,
provider: "anthropic",
model: "claude-opus-4-5",
initialModelLabel: "anthropic/claude-opus-4-5",
formatModelSwitchEvent: (label) => `Switched to ${label}`,
});
expect(result?.text).toContain("failed");
expect(result?.text).toContain("session state unavailable");
});
it("shows error message when sessionStore is missing", async () => {
const directives = parseInlineDirectives("/model openai/gpt-4o");
const sessionEntry: SessionEntry = {
sessionId: "s1",
updatedAt: Date.now(),
};
const result = await handleDirectiveOnly({
cfg: baseConfig(),
directives,
sessionEntry,
sessionStore: undefined, // Missing!
sessionKey: "agent:main:dm:1",
storePath: "/tmp/sessions.json",
elevatedEnabled: false,
elevatedAllowed: false,
defaultProvider: "anthropic",
defaultModel: "claude-opus-4-5",
aliasIndex: baseAliasIndex(),
allowedModelKeys,
allowedModelCatalog,
resetModelOverride: false,
provider: "anthropic",
model: "claude-opus-4-5",
initialModelLabel: "anthropic/claude-opus-4-5",
formatModelSwitchEvent: (label) => `Switched to ${label}`,
});
expect(result?.text).toContain("failed");
expect(result?.text).toContain("session state unavailable");
});
it("shows error message when sessionKey is missing", async () => {
const directives = parseInlineDirectives("/model openai/gpt-4o");
const sessionEntry: SessionEntry = {
sessionId: "s1",
updatedAt: Date.now(),
};
const sessionStore = { "agent:main:dm:1": sessionEntry };
const result = await handleDirectiveOnly({
cfg: baseConfig(),
directives,
sessionEntry,
sessionStore,
sessionKey: undefined, // Missing!
storePath: "/tmp/sessions.json",
elevatedEnabled: false,
elevatedAllowed: false,
defaultProvider: "anthropic",
defaultModel: "claude-opus-4-5",
aliasIndex: baseAliasIndex(),
allowedModelKeys,
allowedModelCatalog,
resetModelOverride: false,
provider: "anthropic",
model: "claude-opus-4-5",
initialModelLabel: "anthropic/claude-opus-4-5",
formatModelSwitchEvent: (label) => `Switched to ${label}`,
});
expect(result?.text).toContain("failed");
expect(result?.text).toContain("session state unavailable");
});
it("shows no model message when no /model directive", async () => {
const directives = parseInlineDirectives("hello world");
const sessionEntry: SessionEntry = {

View File

@@ -62,8 +62,8 @@ function resolveExecDefaults(params: {
export async function handleDirectiveOnly(params: {
cfg: ClawdbotConfig;
directives: InlineDirectives;
sessionEntry?: SessionEntry;
sessionStore?: Record<string, SessionEntry>;
sessionEntry: SessionEntry;
sessionStore: Record<string, SessionEntry>;
sessionKey: string;
storePath?: string;
elevatedEnabled: boolean;
@@ -288,8 +288,6 @@ export async function handleDirectiveOnly(params: {
nextThinkLevel === "xhigh" &&
!supportsXHighThinking(resolvedProvider, resolvedModel);
let didPersistModel = false;
if (sessionEntry && sessionStore && sessionKey) {
const prevElevatedLevel =
currentElevatedLevel ??
(sessionEntry.elevatedLevel as ElevatedLevel | undefined) ??
@@ -347,7 +345,6 @@ export async function handleDirectiveOnly(params: {
selection: modelSelection,
profileOverride,
});
didPersistModel = true;
}
if (directives.hasQueueDirective && directives.queueReset) {
delete sessionEntry.queueMode;
@@ -396,7 +393,6 @@ export async function handleDirectiveOnly(params: {
contextKey: "mode:reasoning",
});
}
}
const parts: string[] = [];
if (directives.hasThinkDirective && directives.thinkLevel) {
@@ -449,7 +445,7 @@ export async function handleDirectiveOnly(params: {
`Thinking level set to high (xhigh not supported for ${resolvedProvider}/${resolvedModel}).`,
);
}
if (modelSelection && didPersistModel) {
if (modelSelection) {
const label = `${modelSelection.provider}/${modelSelection.model}`;
const labelWithAlias = modelSelection.alias ? `${modelSelection.alias} (${label})` : label;
parts.push(
@@ -460,10 +456,6 @@ export async function handleDirectiveOnly(params: {
if (profileOverride) {
parts.push(`Auth profile set to ${profileOverride}.`);
}
} else if (modelSelection && !didPersistModel) {
parts.push(
`Model switch to ${modelSelection.provider}/${modelSelection.model} failed (session state unavailable).`,
);
}
if (directives.hasQueueDirective && directives.queueMode) {
parts.push(formatDirectiveAck(`Queue mode set to ${directives.queueMode}.`));

View File

@@ -39,8 +39,8 @@ export async function applyInlineDirectiveOverrides(params: {
agentId: string;
agentDir: string;
agentCfg: AgentDefaults;
sessionEntry?: SessionEntry;
sessionStore?: Record<string, SessionEntry>;
sessionEntry: SessionEntry;
sessionStore: Record<string, SessionEntry>;
sessionKey: string;
storePath?: string;
sessionScope: Parameters<typeof buildStatusReply>[0]["sessionScope"];

View File

@@ -89,8 +89,8 @@ export async function resolveReplyDirectives(params: {
workspaceDir: string;
agentCfg: AgentDefaults;
sessionCtx: TemplateContext;
sessionEntry?: SessionEntry;
sessionStore?: Record<string, SessionEntry>;
sessionEntry: SessionEntry;
sessionStore: Record<string, SessionEntry>;
sessionKey: string;
storePath?: string;
sessionScope: Parameters<typeof applyInlineDirectiveOverrides>[0]["sessionScope"];