mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-18 15:47:27 +00:00
fix(session): preserve verbose/thinking/tts overrides across /new and /reset (openclaw#10881) thanks @mcaxtr
Verified: - pnpm install --frozen-lockfile - pnpm build - pnpm check - pnpm test Co-authored-by: mcaxtr <7562095+mcaxtr@users.noreply.github.com> Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
This commit is contained in:
@@ -34,6 +34,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Security/Hooks: harden webhook and device token verification with shared constant-time secret comparison, and add per-client auth-failure throttling for hook endpoints (`429` + `Retry-After`). Thanks @akhmittra.
|
||||
- Security/Browser: require auth for loopback browser control HTTP routes, auto-generate `gateway.auth.token` when browser control starts without auth, and add a security-audit check for unauthenticated browser control. Thanks @tcusolle.
|
||||
- Sessions/Gateway: harden transcript path resolution and reject unsafe session IDs/file paths so session operations stay within agent sessions directories. Thanks @akhmittra.
|
||||
- Sessions: preserve `verboseLevel`, `thinkingLevel`/`reasoningLevel`, and `ttsAuto` overrides across `/new` and `/reset` session resets. (#10787) Thanks @mcaxtr.
|
||||
- Gateway: raise WS payload/buffer limits so 5,000,000-byte image attachments work reliably. (#14486) Thanks @0xRaini.
|
||||
- Logging/CLI: use local timezone timestamps for console prefixing, and include `±HH:MM` offsets when using `openclaw logs --local-time` to avoid ambiguity. (#14771) Thanks @0xRaini.
|
||||
- Gateway: drain active turns before restart to prevent message loss. (#13931) Thanks @0xRaini.
|
||||
|
||||
@@ -451,6 +451,168 @@ describe("applyResetModelOverride", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("initSessionState preserves behavior overrides across /new and /reset", () => {
|
||||
async function createStorePath(prefix: string): Promise<string> {
|
||||
const root = await fs.mkdtemp(path.join(os.tmpdir(), prefix));
|
||||
return path.join(root, "sessions.json");
|
||||
}
|
||||
|
||||
async function seedSessionStoreWithOverrides(params: {
|
||||
storePath: string;
|
||||
sessionKey: string;
|
||||
sessionId: string;
|
||||
overrides: Record<string, unknown>;
|
||||
}): Promise<void> {
|
||||
const { saveSessionStore } = await import("../../config/sessions.js");
|
||||
await saveSessionStore(params.storePath, {
|
||||
[params.sessionKey]: {
|
||||
sessionId: params.sessionId,
|
||||
updatedAt: Date.now(),
|
||||
...params.overrides,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
it("/new preserves verboseLevel from previous session", async () => {
|
||||
const storePath = await createStorePath("openclaw-reset-verbose-");
|
||||
const sessionKey = "agent:main:telegram:dm:user1";
|
||||
const existingSessionId = "existing-session-verbose";
|
||||
await seedSessionStoreWithOverrides({
|
||||
storePath,
|
||||
sessionKey,
|
||||
sessionId: existingSessionId,
|
||||
overrides: { verboseLevel: "on" },
|
||||
});
|
||||
|
||||
const cfg = {
|
||||
session: { store: storePath, idleMinutes: 999 },
|
||||
} as OpenClawConfig;
|
||||
|
||||
const result = await initSessionState({
|
||||
ctx: {
|
||||
Body: "/new",
|
||||
RawBody: "/new",
|
||||
CommandBody: "/new",
|
||||
From: "user1",
|
||||
To: "bot",
|
||||
ChatType: "direct",
|
||||
SessionKey: sessionKey,
|
||||
Provider: "telegram",
|
||||
Surface: "telegram",
|
||||
},
|
||||
cfg,
|
||||
commandAuthorized: true,
|
||||
});
|
||||
|
||||
expect(result.isNewSession).toBe(true);
|
||||
expect(result.resetTriggered).toBe(true);
|
||||
expect(result.sessionId).not.toBe(existingSessionId);
|
||||
expect(result.sessionEntry.verboseLevel).toBe("on");
|
||||
});
|
||||
|
||||
it("/reset preserves thinkingLevel and reasoningLevel from previous session", async () => {
|
||||
const storePath = await createStorePath("openclaw-reset-thinking-");
|
||||
const sessionKey = "agent:main:telegram:dm:user2";
|
||||
const existingSessionId = "existing-session-thinking";
|
||||
await seedSessionStoreWithOverrides({
|
||||
storePath,
|
||||
sessionKey,
|
||||
sessionId: existingSessionId,
|
||||
overrides: { thinkingLevel: "full", reasoningLevel: "high" },
|
||||
});
|
||||
|
||||
const cfg = {
|
||||
session: { store: storePath, idleMinutes: 999 },
|
||||
} as OpenClawConfig;
|
||||
|
||||
const result = await initSessionState({
|
||||
ctx: {
|
||||
Body: "/reset",
|
||||
RawBody: "/reset",
|
||||
CommandBody: "/reset",
|
||||
From: "user2",
|
||||
To: "bot",
|
||||
ChatType: "direct",
|
||||
SessionKey: sessionKey,
|
||||
Provider: "telegram",
|
||||
Surface: "telegram",
|
||||
},
|
||||
cfg,
|
||||
commandAuthorized: true,
|
||||
});
|
||||
|
||||
expect(result.isNewSession).toBe(true);
|
||||
expect(result.resetTriggered).toBe(true);
|
||||
expect(result.sessionEntry.thinkingLevel).toBe("full");
|
||||
expect(result.sessionEntry.reasoningLevel).toBe("high");
|
||||
});
|
||||
|
||||
it("/new preserves ttsAuto from previous session", async () => {
|
||||
const storePath = await createStorePath("openclaw-reset-tts-");
|
||||
const sessionKey = "agent:main:telegram:dm:user3";
|
||||
const existingSessionId = "existing-session-tts";
|
||||
await seedSessionStoreWithOverrides({
|
||||
storePath,
|
||||
sessionKey,
|
||||
sessionId: existingSessionId,
|
||||
overrides: { ttsAuto: "on" },
|
||||
});
|
||||
|
||||
const cfg = {
|
||||
session: { store: storePath, idleMinutes: 999 },
|
||||
} as OpenClawConfig;
|
||||
|
||||
const result = await initSessionState({
|
||||
ctx: {
|
||||
Body: "/new",
|
||||
RawBody: "/new",
|
||||
CommandBody: "/new",
|
||||
From: "user3",
|
||||
To: "bot",
|
||||
ChatType: "direct",
|
||||
SessionKey: sessionKey,
|
||||
Provider: "telegram",
|
||||
Surface: "telegram",
|
||||
},
|
||||
cfg,
|
||||
commandAuthorized: true,
|
||||
});
|
||||
|
||||
expect(result.isNewSession).toBe(true);
|
||||
expect(result.sessionEntry.ttsAuto).toBe("on");
|
||||
});
|
||||
|
||||
it("idle-based new session does NOT preserve overrides (no entry to read)", async () => {
|
||||
const storePath = await createStorePath("openclaw-idle-no-preserve-");
|
||||
const sessionKey = "agent:main:telegram:dm:new-user";
|
||||
|
||||
const cfg = {
|
||||
session: { store: storePath, idleMinutes: 0 },
|
||||
} as OpenClawConfig;
|
||||
|
||||
const result = await initSessionState({
|
||||
ctx: {
|
||||
Body: "hello",
|
||||
RawBody: "hello",
|
||||
CommandBody: "hello",
|
||||
From: "new-user",
|
||||
To: "bot",
|
||||
ChatType: "direct",
|
||||
SessionKey: sessionKey,
|
||||
Provider: "telegram",
|
||||
Surface: "telegram",
|
||||
},
|
||||
cfg,
|
||||
commandAuthorized: true,
|
||||
});
|
||||
|
||||
expect(result.isNewSession).toBe(true);
|
||||
expect(result.resetTriggered).toBe(false);
|
||||
expect(result.sessionEntry.verboseLevel).toBeUndefined();
|
||||
expect(result.sessionEntry.thinkingLevel).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe("prependSystemEvents", () => {
|
||||
it("adds a local timestamp to queued system events by default", async () => {
|
||||
vi.useFakeTimers();
|
||||
|
||||
@@ -240,6 +240,15 @@ export async function initSessionState(params: {
|
||||
isNewSession = true;
|
||||
systemSent = false;
|
||||
abortedLastRun = false;
|
||||
// When a reset trigger (/new, /reset) starts a new session, carry over
|
||||
// user-set behavior overrides (verbose, thinking, reasoning, ttsAuto)
|
||||
// so the user doesn't have to re-enable them every time.
|
||||
if (resetTriggered && entry) {
|
||||
persistedThinking = entry.thinkingLevel;
|
||||
persistedVerbose = entry.verboseLevel;
|
||||
persistedReasoning = entry.reasoningLevel;
|
||||
persistedTtsAuto = entry.ttsAuto;
|
||||
}
|
||||
}
|
||||
|
||||
const baseEntry = !isNewSession && freshEntry ? entry : undefined;
|
||||
|
||||
Reference in New Issue
Block a user