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:
@@ -30,6 +30,7 @@ import type {
|
||||
ClusterLabeling,
|
||||
EmbeddingsLedger,
|
||||
} from "./contracts.ts";
|
||||
import type { UserSettings } from "./settings.ts";
|
||||
|
||||
// ─── ChatStore — the session record's home (data/chats) ─────────────────────
|
||||
|
||||
@@ -148,3 +149,44 @@ export interface JobStore {
|
||||
/** The job fulfilling a given tool part, if any (spawn-hook dedup). */
|
||||
findJobByToolCallId(chatId: string, toolCallId: string): JobRecord | null;
|
||||
}
|
||||
|
||||
// ─── SettingsStore — THE user settings record (settings-storage wave, 2026-09-19) ──
|
||||
|
||||
/**
|
||||
* The one record for every non-secret product preference (core/settings.ts
|
||||
* UserSettings — delta semantics, see that module's doc). The store is
|
||||
* replace-whole on the RECORD; per-section scoping is a server-band concern
|
||||
* (single process, no stale-client hazard to defend against at this seam).
|
||||
* Lazy migration (default-settings.json → the record's chat section, schema
|
||||
* version bumps) happens inside readSettings, never in the callers.
|
||||
*/
|
||||
export interface SettingsStore {
|
||||
/** The sanitized record, or null when none exists yet (first boot). */
|
||||
readSettings(): UserSettings | null;
|
||||
/** Persist the record (replace-whole). Callers pass a SANITIZED record. */
|
||||
writeSettings(settings: UserSettings): void;
|
||||
}
|
||||
|
||||
// ─── PromptOverrideStore — per-segment system-prompt overrides (2026-09-19) ──
|
||||
|
||||
/**
|
||||
* User text overriding ONE source prompt segment (the shared base or one
|
||||
* numeric fragment), stored at the data-dir seam mirroring the source
|
||||
* layout (config.PROMPTS_OVERRIDE_DIR). File presence is NOT the override:
|
||||
* the settings record's agent.prompts switch is the explicit on/off, so a
|
||||
* user can keep a customized file on disk while running the source default.
|
||||
* The segment LIST is always derived from source at runtime — see the
|
||||
* adapter (src/store/prompt-overrides.ts) for the loader rules.
|
||||
*/
|
||||
export interface PromptOverrideStore {
|
||||
/** Every override path currently on disk ("system-prompt.md",
|
||||
* "teacher/20-teaching.md", ...), POSIX-relative, sorted. */
|
||||
listOverrides(): string[];
|
||||
/** The override text, or null when the path has no file. */
|
||||
readOverride(segmentPath: string): string | null;
|
||||
/** Persist override text for a segment path (creates parents). */
|
||||
writeOverride(segmentPath: string, text: string): void;
|
||||
/** Remove the override file, if present (switches are untouched — an
|
||||
* on-switch with no file composes the source default, inert). */
|
||||
deleteOverride(segmentPath: string): void;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user