wave 2 (platform stream): settings storage & plumbing foundation (P2)

- src/core/settings.ts: versioned DELTA record schema + sanitizer (unknown keys preserved)
  — general/chat/agent{models,tools,prompts}/appearance/graph sections
- src/core/settings-resolution.ts: pure resolveSettings (factory defaults → user delta),
  extraction/graph-context channel predicates
- src/store/settings.ts + prompt-overrides.ts: SettingsStore/PromptOverrideStore adapters
  wired through stores.ts (effectiveSettings() fresh per call); lazy migration of
  default-settings.json into record.chat
- app/api/settings/** routes (record, section bodies, prompt overrides per segment)
- chat-handler: graph gates at trigger call-sites + toolToggles injection seam
- config.ts: SETTINGS_FILE + PROMPTS_OVERRIDE_DIR paths

587/587 tests; typecheck/lint/build clean
This commit is contained in:
amosblomqvist
2026-09-19 18:05:30 +03:00
parent 36c5d19437
commit cb4fe25c89
24 changed files with 2096 additions and 101 deletions
+16 -2
View File
@@ -1,6 +1,6 @@
import { describe, it } from "node:test";
import assert from "node:assert/strict";
import { modelCatalog, validateChatSettings, catalogDefaults, toModelDto } from "../src/server/model-catalog.ts";
import { modelCatalog, validateChatSettings, catalogDefaultsFor, toModelDto } from "../src/server/model-catalog.ts";
import { clampThinkingLevel, getSupportedThinkingLevels } from "@earendil-works/pi-ai";
import { MODELS } from "../config.ts";
import { piEngineFor } from "../src/agent/models.ts";
@@ -32,11 +32,25 @@ describe("the model catalog projection", () => {
});
it("defaults mirror the conversation agents' config pin", () => {
const d = catalogDefaults();
const d = catalogDefaultsFor();
assert.deepEqual(d, { model: { provider: "openrouter", id: MODELS.prober.model }, thinkingLevel: MODELS.prober.effort });
assert.equal(d.model.id, MODELS.teacher.model, "one controller: prober and teacher share the pin");
});
it("a picked default in the record's chat section overrides the pin (catalogDefaultsFor, settings wave)", () => {
const d = catalogDefaultsFor({ model: { provider: "openrouter", id: "qwen/qwen3.8-flash" }, thinkingLevel: "high" });
assert.deepEqual(d, { model: { provider: "openrouter", id: "qwen/qwen3.8-flash" }, thinkingLevel: "high" });
// partial: only the model chosen → thinking falls back to the pin
const partial = catalogDefaultsFor({ model: { provider: "openrouter", id: "qwen/qwen3.8-flash" } });
assert.equal(partial.thinkingLevel, MODELS.prober.effort);
assert.equal(partial.model.id, "qwen/qwen3.8-flash");
// graphContextTokens-only record: model untouched
assert.deepEqual(catalogDefaultsFor({ graphContextTokens: 512 }), {
model: { provider: "openrouter", id: MODELS.prober.model },
thinkingLevel: MODELS.prober.effort,
});
});
it("toModelDto projects the raw catalog model verbatim", () => {
const raw = piEngineFor("prober").model; // the catalog's glm entry
const dto = toModelDto(raw);