Commit Graph
8 Commits
Author SHA1 Message Date
amosblomqvist 88266eaaf4 Merge PR114 dictation foundation for approved provider-row finishing
Preserve original contributor commit 3bb28e5f29. Resolve only the settings-dialog conflict by retaining current General/update behavior: the donor General-only language placement is explicitly superseded by the human-approved row BELOW the full-width Deepgram key. The assigned implementation worker will implement that placement and approved shared dropdown finishing. Foundation only, not a checked candidate.
2026-10-27 21:33:40 +02:00
amosblomqvist 88ba42fd37 fix: allow repairing model slots after subscription disconnect 2026-10-26 18:50:54 +02:00
Bartosz Chronowki 3bb28e5f29 feat(dictation): add persistent language selection in General settings 2026-10-25 14:33:49 +01:00
amosblomqvist ffc4df1938 compaction: the surface — settings row, two ruled dividers, summary dialog, send gate (round 14 slice 3)
- settings: compaction.thresholdTokens row on the Agent tab (owner pick)
  in a Context group — CommitInput mirroring the graph budget row, hint
  resolved through the SAME core resolver the trigger reads; PUT rejects
  below the 32k floor with the anti-churn rationale; own section
  (compaction) in SETTINGS_SECTIONS + the settings-signal union
- view fold: one marker → two dividers — the minimal MOMENT divider at
  the marker (retrying-message register, no rule lines) and the longer
  CUT divider emitted immediately before the first kept message
  (phase-divider geometry, chip carries '· Nk tok'); both carry the full
  summary payload; full history stays visible to the user
- components/compaction-divider.tsx: the dividers + summary dialog
  (pure exported body for SSR pins, the delete-dialog pattern; Esc +
  Close dismiss, backdrop does not)
- chat.tsx: compaction-changed SSE flips the running state — composer
  canSend, onSend/doSend/doSteerOrSend, and both docks' submit + hint
  ('compacting session…') gate on it (the server's registry mutex is the
  structural gate; this is the affordance); dialog mounts beside the
  abort dialog
- pins: settings-body (4), view fold (2), divider SSR (4)
2026-10-05 22:51:08 +03:00
amosblomqvist e79f85ebe4 Prompt-override orphan trap: delete clears the switch, writes heal orphans
Contract-fix interlude (2026-09-26), two bugs from UI's prompts-overhaul
(6edc220):

(a) ORPHAN-400 TRAP: an ON switch whose override file dies orphaned the
record; every agent-section write then 400'd (ON-must-have-file) while
the disabled toggle couldn't reach the OFF escape hatch — a user stuck
unable to write settings. Verified live on 3004 before the fix.
Two-layer fix:
- DELETE /api/settings/prompts/[...segment] now clears the record's
  switch in the same operation (switch-first order: a crash between the
  two leaves the valid off+file state, never an orphan). Response gains
  switchCleared (additive). Overrules the 2026-09-19 switch-survives-
  delete note — the delete route and the record agree by construction.
- PUT /api/settings/[section] agent writes HEAL an orphaned ON to OFF
  (key dropped) inside the write — never a 400 (defense in depth: disk
  wipes outside the app can still orphan). A valid ON with its file
  stays valid; the invariant: a user can never get stuck unable to
  write settings, and the record never contradicts file reality for
  long. Heal logs at info.

(b) TEST WIPES REAL DATA: tests/prompt-overrides.test.ts wiped
data/prompts/ (REAL user override files) in after() and its empty-list
case — every npm test killed actual overrides and orphaned their ON
switches (feeding trap a). Isolated via the established LH_DATA_DIR
seam (mkdtemp + env set before the first config.ts-loading import; all
app-module imports dynamic). after() now snapshots the REAL repo
data/prompts before/after and fails the suite if it changed; the seam is
pinned (writes land under the temp dir).

Pins: delete-clears-switch (incl. idempotent no-op), the exact repro
(disk-level wipe → mirror-the-orphan + tool toggle → 200, healed key,
rest of the write landed), vanished-fragment ON heals, valid ON stays.
Live: the 400-forever write now lands 200, heal info-logged, record
clean.

Gates: 967/967 (+5), typecheck, lint, build green.
2026-09-25 19:07:12 +03:00
amosblomqvist 9387418864 feat(models): clamp-on-write for pinned thinking levels + DEFAULT_THINKING_ORDER in the core barrel
Fix 1 — clamp, never reject (owner ruling, interlude 2026-09-22): a
model swap with a pinned thinking level must JUST WORK. The owner hit
the write-side 400 live (Claude Fable 5.1/xhigh → GLM 5.3 flash:
"z-ai/glm-5.3-flash does not support thinking level xhigh"). The
supported-level verdicts in validateChatSettings and
validateModelsSettings are DEAD — the WRITE clamps the pin to the new
model's nearest supported level (pi's clampThinkingLevel, the same
translation piEngineForSpec applies at runtime) and stores the clamped
result; the server is the truth — the record shows the clamped level
after write.

New pure helpers in model-catalog.ts, applied by ALL THREE write
routes (section, defaults, per-chat — one verdict seam, so every
surface that used the old 400 gets the clamp uniformly):
- clampModelsLevels: every slot's level clamps against the SLOT'S OWN
  model (the parser already requires a model on each slot write).
- clampChatLevel: the body's own model, else the record's resolved
  strong slot (the chain's fallback); no model anywhere → rides
  unchecked (runtime clamps at engine build; the turn gate refuses
  the model-less chat anyway).

Fix 2 — DEFAULT_THINKING_ORDER moves from config.ts to core/settings
(core barrel, AUTH_KEYS precedent — one source of truth, no drift).
The UI imports it to auto-populate a thinking level on FRESH model
picks; the ordering has no other client role. Legacy unset records
keep the runtime defaultThinkingLevel fallback at engine build
untouched. Import sites updated: agent/models.ts, tests.

Verified live on 3004: the owner's exact case stores max (not 400);
a bare chat-level write clamps to low against the strong slot's glm;
unknown provider/model still 400s with the provider list.

Tests: the level-verdict suites became clamp suites (the owner's case
pinned verbatim: xhigh→max, minimal→low, off→low, supported
pass-through, level-less slots ride); the machine-aware
expectBareLevelVerdict chain is retired — clampChatLevel takes the
strong slot as an explicit param, so the tests are pure again.

Gates: typecheck, lint, 839/839, build.
2026-09-22 21:44:43 +03:00
amosblomqvist f9e3a4c43d feat(models): strong/cheap/vision model slots + multi-provider engine federation
Model slots wave (2026-09-20), chunk B:

- settings: new `models` section {strong, cheap, vision} slots (each
  field independent, same shape as a role override); ROLE_SLOT maps all
  nine roles to tiers (strong=prober+teacher, cheap=researcher/
  demoBuilder/extractor/auditor/labeler, vision=visualMaker+pdfReader);
  resolution chain per role: override → slot → factory pin.
- config: ModelSpec gains provider (all pins openrouter, the BYOK
  default); MODEL_PINS in stores.ts now spans all nine roles (the strong
  slot's factory rep IS the prober pin).
- engine: piModels() registers EVERY builtin provider catalog;
  piEngineForSpec(resolved spec) is the new engine factory (pi-ai
  resolves per-provider keys from env itself); piEngineFor stays as the
  config-pin fallback. chat-handler resolves the conversation chain
  (chat override → strong slot → pin) and injects engineFor into the job
  runner, closing the old gap where per-role overrides never reached
  job engines.
- /api/models: models federate ONLY keyed providers (providers surface
  with keyPresent, env-wins, fresh per call); slots (resolved tier pins)
  join rolePins; catalogDefaultsFor inherits the strong slot; the models
  settings section gets the same catalog gate as chat.
- pricing: PRICES/CONTEXT_LIMITS uniformly keyed provider:id
  (regenerated from ALL pi-ai catalogs, 1354 keys); usage stamps
  (TurnUsage, JobUsage, extraction stages) switch to provider:id —
  owner decision: no legacy compat, old stamps render unpriced.
2026-09-22 00:14:56 +03:00
amosblomqvist cb4fe25c89 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
2026-09-19 18:05:30 +03:00