mirror of
https://github.com/moltbot/moltbot.git
synced 2026-05-06 15:18:58 +00:00
fix(ui): resolve session thinking defaults
This commit is contained in:
@@ -152,7 +152,7 @@ test("sessions.list uses the gateway model catalog for effective thinking defaul
|
||||
sessions: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
key: "agent:main:main",
|
||||
thinkingDefault: undefined,
|
||||
thinkingDefault: "medium",
|
||||
thinkingOptions: ["off", "minimal", "low", "medium", "high"],
|
||||
}),
|
||||
]),
|
||||
|
||||
@@ -374,6 +374,7 @@ type SessionListRowContext = {
|
||||
storeChildSessionsByKey: Map<string, string[]>;
|
||||
selectedModelByOverrideRef: Map<string, ReturnType<typeof resolveSessionModelRef>>;
|
||||
thinkingLevelsByModelRef: Map<string, ReturnType<typeof listThinkingLevelOptions>>;
|
||||
thinkingDefaultByAgentModelRef: Map<string, string>;
|
||||
};
|
||||
|
||||
function resolveRuntimeChildSessionKeys(
|
||||
@@ -492,6 +493,7 @@ function buildSessionListRowContext(params: {
|
||||
storeChildSessionsByKey: buildStoreChildSessionIndex(params.store, params.now, subagentRuns),
|
||||
selectedModelByOverrideRef: new Map(),
|
||||
thinkingLevelsByModelRef: new Map(),
|
||||
thinkingDefaultByAgentModelRef: new Map(),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -499,6 +501,17 @@ function createSessionRowModelCacheKey(provider: string | undefined, model: stri
|
||||
return `${normalizeLowercaseStringOrEmpty(provider)}\0${normalizeOptionalString(model) ?? ""}`;
|
||||
}
|
||||
|
||||
function createSessionRowThinkingDefaultCacheKey(params: {
|
||||
agentId: string | undefined;
|
||||
provider: string | undefined;
|
||||
model: string | undefined;
|
||||
}) {
|
||||
return `${normalizeAgentId(params.agentId)}\0${createSessionRowModelCacheKey(
|
||||
params.provider,
|
||||
params.model,
|
||||
)}`;
|
||||
}
|
||||
|
||||
function resolveSessionSelectedModelRef(params: {
|
||||
cfg: OpenClawConfig;
|
||||
entry?: SessionEntry;
|
||||
@@ -548,6 +561,60 @@ function resolveSessionRowThinkingLevels(params: {
|
||||
return levels;
|
||||
}
|
||||
|
||||
function resolveSessionRowThinkingDefault(params: {
|
||||
cfg: OpenClawConfig;
|
||||
provider: string;
|
||||
model: string;
|
||||
agentId: string;
|
||||
modelCatalog?: ModelCatalogEntry[];
|
||||
rowContext?: SessionListRowContext;
|
||||
}): string {
|
||||
if (!params.rowContext) {
|
||||
return resolveGatewaySessionThinkingDefault({
|
||||
cfg: params.cfg,
|
||||
provider: params.provider,
|
||||
model: params.model,
|
||||
agentId: params.agentId,
|
||||
modelCatalog: params.modelCatalog,
|
||||
});
|
||||
}
|
||||
const key = createSessionRowThinkingDefaultCacheKey(params);
|
||||
if (params.rowContext.thinkingDefaultByAgentModelRef.has(key)) {
|
||||
return params.rowContext.thinkingDefaultByAgentModelRef.get(key)!;
|
||||
}
|
||||
const defaultLevel = resolveGatewaySessionThinkingDefault({
|
||||
cfg: params.cfg,
|
||||
provider: params.provider,
|
||||
model: params.model,
|
||||
agentId: params.agentId,
|
||||
modelCatalog: params.modelCatalog,
|
||||
});
|
||||
params.rowContext.thinkingDefaultByAgentModelRef.set(key, defaultLevel);
|
||||
return defaultLevel;
|
||||
}
|
||||
|
||||
function seedSessionRowContextDefaultThinking(params: {
|
||||
rowContext: SessionListRowContext;
|
||||
cfg: OpenClawConfig;
|
||||
defaults: GatewaySessionsDefaults;
|
||||
}) {
|
||||
if (
|
||||
!params.defaults.modelProvider ||
|
||||
!params.defaults.model ||
|
||||
!params.defaults.thinkingDefault
|
||||
) {
|
||||
return;
|
||||
}
|
||||
params.rowContext.thinkingDefaultByAgentModelRef.set(
|
||||
createSessionRowThinkingDefaultCacheKey({
|
||||
agentId: resolveDefaultAgentId(params.cfg),
|
||||
provider: params.defaults.modelProvider,
|
||||
model: params.defaults.model,
|
||||
}),
|
||||
params.defaults.thinkingDefault,
|
||||
);
|
||||
}
|
||||
|
||||
function mergeChildSessionKeys(
|
||||
runtimeChildSessions: string[] | undefined,
|
||||
storeChildSessions: string[] | undefined,
|
||||
@@ -1702,6 +1769,14 @@ export function buildGatewaySessionRow(params: {
|
||||
modelCatalog: params.modelCatalog,
|
||||
rowContext,
|
||||
});
|
||||
const thinkingDefault = resolveSessionRowThinkingDefault({
|
||||
cfg,
|
||||
provider: thinkingProvider,
|
||||
model: thinkingModel,
|
||||
agentId: sessionAgentId,
|
||||
modelCatalog: params.modelCatalog,
|
||||
rowContext,
|
||||
});
|
||||
const pluginExtensions =
|
||||
!lightweight && entry ? projectPluginSessionExtensionsSync({ sessionKey: key, entry }) : [];
|
||||
|
||||
@@ -1731,15 +1806,7 @@ export function buildGatewaySessionRow(params: {
|
||||
thinkingLevel: entry?.thinkingLevel,
|
||||
thinkingLevels,
|
||||
thinkingOptions: thinkingLevels.map((level) => level.label),
|
||||
thinkingDefault: lightweight
|
||||
? entry?.thinkingLevel
|
||||
: resolveGatewaySessionThinkingDefault({
|
||||
cfg,
|
||||
provider: thinkingProvider,
|
||||
model: thinkingModel,
|
||||
agentId: sessionAgentId,
|
||||
modelCatalog: params.modelCatalog,
|
||||
}),
|
||||
thinkingDefault,
|
||||
fastMode: entry?.fastMode,
|
||||
verboseLevel: entry?.verboseLevel,
|
||||
traceLevel: entry?.traceLevel,
|
||||
@@ -2040,6 +2107,10 @@ export function listSessionsFromStore(params: {
|
||||
defaultLimit: SESSIONS_LIST_DEFAULT_LIMIT,
|
||||
});
|
||||
const { entries, totalCount, limitApplied } = selection;
|
||||
const defaults = getSessionDefaults(cfg, params.modelCatalog);
|
||||
if (entries.length > 0) {
|
||||
seedSessionRowContextDefaultThinking({ rowContext: getRowContext(), cfg, defaults });
|
||||
}
|
||||
|
||||
const sessions = entries.map(([key, entry], index) => {
|
||||
const includeTranscriptFields = index < sessionListTranscriptFieldRows;
|
||||
@@ -2066,7 +2137,7 @@ export function listSessionsFromStore(params: {
|
||||
totalCount,
|
||||
limitApplied,
|
||||
hasMore: sessions.length < totalCount,
|
||||
defaults: getSessionDefaults(cfg, params.modelCatalog),
|
||||
defaults,
|
||||
sessions,
|
||||
};
|
||||
}
|
||||
@@ -2109,6 +2180,10 @@ export async function listSessionsFromStoreAsync(params: {
|
||||
defaultLimit: SESSIONS_LIST_DEFAULT_LIMIT,
|
||||
});
|
||||
const { entries, totalCount, limitApplied } = selection;
|
||||
const defaults = getSessionDefaults(cfg, params.modelCatalog);
|
||||
if (entries.length > 0) {
|
||||
seedSessionRowContextDefaultThinking({ rowContext: getRowContext(), cfg, defaults });
|
||||
}
|
||||
|
||||
const sessions: GatewaySessionRow[] = [];
|
||||
for (let i = 0; i < entries.length; i++) {
|
||||
@@ -2167,7 +2242,7 @@ export async function listSessionsFromStoreAsync(params: {
|
||||
totalCount,
|
||||
limitApplied,
|
||||
hasMore: sessions.length < totalCount,
|
||||
defaults: getSessionDefaults(cfg, params.modelCatalog),
|
||||
defaults,
|
||||
sessions,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -5,12 +5,15 @@ import { describe, expect, it, vi } from "vitest";
|
||||
import type { SessionsListResult } from "../types.ts";
|
||||
import { renderSessions, type SessionsProps } from "./sessions.ts";
|
||||
|
||||
function buildResult(session: SessionsListResult["sessions"][number]): SessionsListResult {
|
||||
function buildResult(
|
||||
session: SessionsListResult["sessions"][number],
|
||||
defaults?: Partial<SessionsListResult["defaults"]>,
|
||||
): SessionsListResult {
|
||||
return {
|
||||
ts: Date.now(),
|
||||
path: "(multiple)",
|
||||
count: 1,
|
||||
defaults: { modelProvider: null, model: null, contextTokens: null },
|
||||
defaults: { modelProvider: null, model: null, contextTokens: null, ...defaults },
|
||||
sessions: [session],
|
||||
};
|
||||
}
|
||||
@@ -268,6 +271,41 @@ describe("sessions view", () => {
|
||||
).toBe("Override: adaptive");
|
||||
});
|
||||
|
||||
it("labels inherited thinking from list defaults when lightweight rows omit row defaults", async () => {
|
||||
const container = document.createElement("div");
|
||||
render(
|
||||
renderSessions(
|
||||
buildProps(
|
||||
buildResult(
|
||||
{
|
||||
key: "agent:main:main",
|
||||
kind: "direct",
|
||||
updatedAt: Date.now(),
|
||||
},
|
||||
{
|
||||
modelProvider: "openai-codex",
|
||||
model: "gpt-5.5",
|
||||
thinkingDefault: "high",
|
||||
thinkingLevels: [
|
||||
{ id: "off", label: "off" },
|
||||
{ id: "high", label: "high" },
|
||||
],
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
container,
|
||||
);
|
||||
await Promise.resolve();
|
||||
|
||||
const thinking = container.querySelector("tbody select") as HTMLSelectElement | null;
|
||||
expect(thinking?.value).toBe("");
|
||||
expect(thinking?.options[0]?.textContent?.trim()).toBe("Inherited: high");
|
||||
expect(Array.from(thinking?.options ?? []).map((option) => option.textContent?.trim())).toEqual(
|
||||
["Inherited: high", "Off", "Override: high"],
|
||||
);
|
||||
});
|
||||
|
||||
it("keeps legacy binary thinking labels patching canonical ids", async () => {
|
||||
const container = document.createElement("div");
|
||||
const onPatch = vi.fn();
|
||||
|
||||
@@ -92,16 +92,37 @@ function getAgentIdentity(
|
||||
: null;
|
||||
}
|
||||
|
||||
function rowMatchesSessionDefaults(
|
||||
row: GatewaySessionRow,
|
||||
defaults: SessionsListResult["defaults"] | undefined,
|
||||
): boolean {
|
||||
return (
|
||||
(!row.modelProvider || row.modelProvider === defaults?.modelProvider) &&
|
||||
(!row.model || row.model === defaults?.model)
|
||||
);
|
||||
}
|
||||
|
||||
function resolveThinkLevelOptions(
|
||||
row: GatewaySessionRow,
|
||||
defaults?: SessionsListResult["defaults"],
|
||||
): readonly { value: string; label: string }[] {
|
||||
const defaultLabel = formatInheritedThinkingLabel(row.thinkingDefault);
|
||||
const sessionModelMatchesDefaults = rowMatchesSessionDefaults(row, defaults);
|
||||
const defaultLabel = formatInheritedThinkingLabel(
|
||||
row.thinkingDefault ?? (sessionModelMatchesDefaults ? defaults?.thinkingDefault : undefined),
|
||||
);
|
||||
const options: readonly GatewayThinkingLevelOption[] = row.thinkingLevels?.length
|
||||
? row.thinkingLevels
|
||||
: (row.thinkingOptions?.length ? row.thinkingOptions : DEFAULT_THINK_LEVELS).map((label) => ({
|
||||
id: normalizeThinkingOptionValue(label),
|
||||
label,
|
||||
}));
|
||||
: sessionModelMatchesDefaults && defaults?.thinkingLevels?.length
|
||||
? defaults.thinkingLevels
|
||||
: (row.thinkingOptions?.length
|
||||
? row.thinkingOptions
|
||||
: sessionModelMatchesDefaults && defaults?.thinkingOptions?.length
|
||||
? defaults.thinkingOptions
|
||||
: DEFAULT_THINK_LEVELS
|
||||
).map((label) => ({
|
||||
id: normalizeThinkingOptionValue(label),
|
||||
label,
|
||||
}));
|
||||
return [
|
||||
{ value: "", label: defaultLabel },
|
||||
...options.map((option) => ({
|
||||
@@ -617,7 +638,10 @@ function renderRows(row: GatewaySessionRow, props: SessionsProps) {
|
||||
const updated = row.updatedAt ? formatRelativeTimestamp(row.updatedAt) : t("common.na");
|
||||
const rawThinking = row.thinkingLevel ?? "";
|
||||
const thinking = rawThinking ? normalizeThinkingOptionValue(rawThinking) : "";
|
||||
const thinkLevels = withCurrentLabeledOption(resolveThinkLevelOptions(row), thinking);
|
||||
const thinkLevels = withCurrentLabeledOption(
|
||||
resolveThinkLevelOptions(row, props.result?.defaults),
|
||||
thinking,
|
||||
);
|
||||
const fastMode = row.fastMode === true ? "on" : row.fastMode === false ? "off" : "";
|
||||
const fastLevels = withCurrentLabeledOption(buildFastLevelOptions(), fastMode);
|
||||
const verbose = row.verboseLevel ?? "";
|
||||
|
||||
Reference in New Issue
Block a user