mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 06:30:42 +00:00
fix(gateway): align session thinking defaults (#63418)
Aligns Gateway history and session list thinking-default resolution so backend session state matches the Control UI default label: - `chat.history` now falls back through the shared Gateway session thinking-default resolver. - Explicit session overrides still win, then owning `agents.list[].thinkingDefault`, then global/model/catalog defaults. - `sessions.list` catalog-aware thinking defaults are covered by focused regressions. PR by @jpreagan. Validated in Blacksmith Testbox `tbx_01kq9t1aeqrz1mj598vvqv9dpg`: - `pnpm test:serial src/gateway/session-utils.test.ts src/gateway/server.sessions.gateway-server-sessions-a.test.ts src/gateway/server.chat.gateway-server-chat.test.ts` (141 passed) - `OPENCLAW_TESTBOX=1 pnpm check:changed`
This commit is contained in:
@@ -14,6 +14,7 @@ Docs: https://docs.openclaw.ai
|
||||
|
||||
### Fixes
|
||||
|
||||
- Gateway/sessions: align `chat.history` and `sessions.list` thinking defaults with owning-agent and catalog-aware resolution so Control UI session defaults match backend runtime state. (#63418) Thanks @jpreagan.
|
||||
- Agents/media: register detached `video_generate` and `music_generate` tool run contexts until terminal status, so Discord-backed provider jobs stay live in `/tasks` instead of becoming `lost` when the parent chat run context disappears. Thanks @vincentkoc.
|
||||
- Agents/media: prefer OpenAI image and video providers when the default model uses the OpenAI Codex auth alias, so auto media generation no longer falls through to Fal before GPT Image or Sora. Thanks @vincentkoc.
|
||||
- Tasks/media: infer agent ownership for session-scoped task records so `/tasks` agent-local fallback includes session-backed `video_generate` and other async media jobs even when the current chat session has no linked rows. Thanks @vincentkoc.
|
||||
|
||||
@@ -3,7 +3,6 @@ import path from "node:path";
|
||||
import { CURRENT_SESSION_VERSION, SessionManager } from "@mariozechner/pi-coding-agent";
|
||||
import { resolveSendableOutboundReplyParts } from "openclaw/plugin-sdk/reply-payload";
|
||||
import { resolveAgentWorkspaceDir, resolveSessionAgentId } from "../../agents/agent-scope.js";
|
||||
import { resolveThinkingDefault } from "../../agents/model-selection.js";
|
||||
import { rewriteTranscriptEntriesInSessionFile } from "../../agents/pi-embedded-runner/transcript-rewrite.js";
|
||||
import { ensureSandboxWorkspaceForSession } from "../../agents/sandbox/context.js";
|
||||
import { resolveAgentTimeoutMs } from "../../agents/timeout.js";
|
||||
@@ -99,6 +98,7 @@ import {
|
||||
capArrayByJsonBytes,
|
||||
loadSessionEntry,
|
||||
resolveGatewayModelSupportsImages,
|
||||
resolveGatewaySessionThinkingDefault,
|
||||
resolveDeletedAgentIdFromSessionKey,
|
||||
readSessionMessages,
|
||||
resolveSessionModelRef,
|
||||
@@ -1706,12 +1706,14 @@ export const chatHandlers: GatewayRequestHandlers = {
|
||||
}
|
||||
let thinkingLevel = entry?.thinkingLevel;
|
||||
if (!thinkingLevel) {
|
||||
const catalog = await context.loadGatewayModelCatalog();
|
||||
thinkingLevel = resolveThinkingDefault({
|
||||
const loadedCatalog = await context.loadGatewayModelCatalog().catch(() => undefined);
|
||||
const modelCatalog = Array.isArray(loadedCatalog) ? loadedCatalog : undefined;
|
||||
thinkingLevel = resolveGatewaySessionThinkingDefault({
|
||||
cfg,
|
||||
agentId: sessionAgentId,
|
||||
provider: resolvedSessionModel.provider,
|
||||
model: resolvedSessionModel.model,
|
||||
catalog,
|
||||
modelCatalog,
|
||||
});
|
||||
}
|
||||
const verboseLevel = entry?.verboseLevel ?? cfg.agents?.defaults?.verboseDefault;
|
||||
|
||||
@@ -918,6 +918,45 @@ describe("gateway server chat", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
test("chat.history uses the owning agent thinkingDefault for non-default agent sessions", async () => {
|
||||
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-gw-"));
|
||||
try {
|
||||
testState.sessionStorePath = path.join(dir, "sessions.json");
|
||||
testState.agentConfig = {
|
||||
model: { primary: "openai/gpt-5" },
|
||||
thinkingDefault: "low",
|
||||
};
|
||||
testState.agentsConfig = {
|
||||
list: [
|
||||
{ id: "main", default: true },
|
||||
{ id: "alpha", thinkingDefault: "minimal" },
|
||||
],
|
||||
};
|
||||
await writeSessionStore({
|
||||
entries: {
|
||||
"agent:alpha:main": {
|
||||
sessionId: "sess-alpha",
|
||||
updatedAt: Date.now(),
|
||||
modelProvider: "openai",
|
||||
model: "gpt-5",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const historyRes = await rpcReq<{ thinkingLevel?: string }>(ws, "chat.history", {
|
||||
sessionKey: "agent:alpha:main",
|
||||
});
|
||||
|
||||
expect(historyRes.ok).toBe(true);
|
||||
expect(historyRes.payload?.thinkingLevel).toBe("minimal");
|
||||
} finally {
|
||||
testState.agentConfig = undefined;
|
||||
testState.agentsConfig = undefined;
|
||||
testState.sessionStorePath = undefined;
|
||||
await fs.rm(dir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test("chat.send does not persist verboseLevel for operator.write callers", async () => {
|
||||
await withGatewayServer(async ({ port }) => {
|
||||
await withMainSessionStore(async () => {
|
||||
|
||||
@@ -775,6 +775,63 @@ describe("gateway server sessions", () => {
|
||||
ws.close();
|
||||
});
|
||||
|
||||
test("sessions.list uses the gateway model catalog for effective thinking defaults", async () => {
|
||||
await createSessionStoreDir();
|
||||
testState.agentConfig = {
|
||||
model: { primary: "test-provider/reasoner" },
|
||||
};
|
||||
await writeSessionStore({
|
||||
entries: {
|
||||
main: {
|
||||
sessionId: "sess-main",
|
||||
updatedAt: Date.now(),
|
||||
modelProvider: "test-provider",
|
||||
model: "reasoner",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const respond = vi.fn();
|
||||
const sessionsHandlers = await getSessionsHandlers();
|
||||
const { getRuntimeConfig } = await getGatewayConfigModule();
|
||||
await sessionsHandlers["sessions.list"]({
|
||||
req: {
|
||||
type: "req",
|
||||
id: "req-sessions-list-thinking-default",
|
||||
method: "sessions.list",
|
||||
params: {},
|
||||
},
|
||||
params: {},
|
||||
respond,
|
||||
client: null,
|
||||
isWebchatConnect: () => false,
|
||||
context: {
|
||||
getRuntimeConfig,
|
||||
loadGatewayModelCatalog: async () => [
|
||||
{
|
||||
provider: "test-provider",
|
||||
id: "reasoner",
|
||||
name: "Reasoner",
|
||||
reasoning: true,
|
||||
},
|
||||
],
|
||||
} as never,
|
||||
});
|
||||
|
||||
expect(respond).toHaveBeenCalledWith(
|
||||
true,
|
||||
expect.objectContaining({
|
||||
sessions: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
key: "agent:main:main",
|
||||
thinkingDefault: "medium",
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
undefined,
|
||||
);
|
||||
});
|
||||
|
||||
test("sessions.changed mutation events include live usage metadata", async () => {
|
||||
const { dir } = await createSessionStoreDir();
|
||||
await fs.writeFile(
|
||||
|
||||
@@ -154,7 +154,7 @@ describe("gateway session utils", () => {
|
||||
reasoning === true
|
||||
? [{ id: "off" }, { id: "low" }, { id: "medium" }, { id: "high" }, { id: "max" }]
|
||||
: [{ id: "off" }],
|
||||
defaultLevel: "off",
|
||||
defaultLevel: reasoning === true ? "medium" : "off",
|
||||
}),
|
||||
},
|
||||
});
|
||||
@@ -193,6 +193,8 @@ describe("gateway session utils", () => {
|
||||
"high",
|
||||
"max",
|
||||
]);
|
||||
expect(defaults.thinkingDefault).toBe("medium");
|
||||
expect(row.thinkingDefault).toBe("medium");
|
||||
});
|
||||
|
||||
test("session defaults use configured thinking default", () => {
|
||||
|
||||
@@ -1042,7 +1042,7 @@ export function resolveGatewaySessionStoreTarget(params: {
|
||||
|
||||
export { loadCombinedSessionStoreForGateway } from "../config/sessions/combined-store-gateway.js";
|
||||
|
||||
function resolveGatewaySessionThinkingDefault(params: {
|
||||
export function resolveGatewaySessionThinkingDefault(params: {
|
||||
cfg: OpenClawConfig;
|
||||
provider: string;
|
||||
model: string;
|
||||
|
||||
Reference in New Issue
Block a user