per-session models: every session owns its model + thinking level from birth
The reported bug (2026-09-28): every chat ran glm-5.3-flash regardless of any pick, while composer and settings UIs agreed with each other and lied about the run. Root cause: a fossil default-settings record (settings.json chat section, frozen pre-slots-wave) was stamped verbatim into every new chat at birth by the new-session flow, and resolveConversationSpec prefers the per-chat record over the strong slot forever. Owner redesign — models are per session, always; the strong slot is the new-session default: - POST /api/chats stamps the newborn verbatim from the strong slot (model + thinking level); no client settings PUT on create anymore - the composer control is dual-bound: in-session it reads/writes the session's own record (GET /api/chats/:id, PUT .../settings); on the draft surfaces it binds the strong slot (picking there = changing the default) - thinking levels follow the identical mechanics (confirmed by owner) - the zombie default-settings system dies whole: useDefaultSettings, /api/settings/defaults, src/store/default-settings.ts, the chat section of UserSettings (sanitizer strips fossil copies), the legacy default-settings.json fold, and DEFAULT_SETTINGS_FILE - catalogDefaultsFor collapses to the strong slot alone - thinking display for a legacy unstamped session resolves exactly like the engine (session ?? strong ?? model ladder) Tests: new chat-birth-stamp suite pins the stamp, the unconfigured boot, the unset level, and the copy-not-live-view regression; defaults pins rewritten strong-only; legacy-migration suite deleted.
This commit is contained in:
@@ -11,7 +11,6 @@ export * from "./commit-positions.ts";
|
||||
export * from "./jobs.ts";
|
||||
export * from "./stores.ts";
|
||||
export { readRenderSettings, writeRenderSettings } from "../store/render-settings.ts";
|
||||
export { readDefaultSettings, writeDefaultSettings } from "../store/default-settings.ts";
|
||||
// The settings record + prompt overrides (settings-storage wave, 2026-09-19).
|
||||
// The *Record aliases keep the app/api/settings routes explicit about which
|
||||
// seam they touch (the raw record, not the chat-section convenience view).
|
||||
|
||||
+16
-14
@@ -39,20 +39,24 @@ import { BACKGROUND_MODEL_ROLES, MODEL_SLOTS, type BackgroundModelRole, type Mod
|
||||
/** The picker's client-safe model entry (contract: core/contracts/models.ts). */
|
||||
export type ModelDto = ModelCatalogEntry;
|
||||
|
||||
/** The conversation agents' resolved default (pure form — the record's chat
|
||||
* section + the strong slot in, CatalogDefaults out; `catalogDefaults()`
|
||||
* below is the fs wrapper). BYOK (owner, 2026-09-22): there is no factory
|
||||
* floor — when neither the chat section nor the strong slot names a
|
||||
* model, the default is NULL and the turn gate refuses with the 503
|
||||
* models_not_configured envelope. An unset thinkingLevel resolves from
|
||||
* DEFAULT_THINKING_ORDER at engine build (reported as absent here). */
|
||||
/**
|
||||
* The conversation agents' resolved default (pure form — the strong slot
|
||||
* in, CatalogDefaults out; `catalogDefaults()` below is the fs wrapper).
|
||||
* PER-SESSION MODELS (owner, 2026-09-28): every session owns its model +
|
||||
* thinking level from birth, stamped verbatim from the strong slot — the
|
||||
* STRONG SLOT IS THE NEW-SESSION DEFAULT, and the resolve-time fallback
|
||||
* for legacy settings-less records. BYOK (owner, 2026-09-22): there is no
|
||||
* factory floor — when the strong slot names no model, the default is
|
||||
* NULL and the turn gate refuses with the 503 models_not_configured
|
||||
* envelope. An unset thinkingLevel resolves from DEFAULT_THINKING_ORDER at
|
||||
* engine build (reported as absent here).
|
||||
*/
|
||||
export function catalogDefaultsFor(
|
||||
chat: ChatSettings | undefined,
|
||||
strong: EffectiveSlot | null,
|
||||
): CatalogDefaults | null {
|
||||
const model = chat?.model ?? (strong ? { provider: strong.provider, id: strong.id } : undefined);
|
||||
const model = strong ? { provider: strong.provider, id: strong.id } : undefined;
|
||||
if (!model) return null;
|
||||
const level = chat?.thinkingLevel ?? strong?.thinkingLevel;
|
||||
const level = strong?.thinkingLevel;
|
||||
return {
|
||||
model,
|
||||
...(level !== undefined ? { thinkingLevel: level } : {}),
|
||||
@@ -60,11 +64,9 @@ export function catalogDefaultsFor(
|
||||
}
|
||||
|
||||
/** The conversation agents' resolved default — what a settings-less chat
|
||||
* runs. (settings-storage wave, 2026-09-19): the user's picked default
|
||||
* rides in the settings record's `chat` section; the picker's defaults
|
||||
* row shows it once chosen. */
|
||||
* runs: the strong slot, i.e. the birth-default for new sessions. */
|
||||
export function catalogDefaults(): CatalogDefaults | null {
|
||||
return catalogDefaultsFor(settingsStore.readSettings()?.chat, effectiveSettings().models.strong);
|
||||
return catalogDefaultsFor(effectiveSettings().models.strong);
|
||||
}
|
||||
|
||||
/** Project the resolved per-role specs to the client DTO (pure — the
|
||||
|
||||
Reference in New Issue
Block a user