This repository has been archived on 2026-04-03. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
core/CHANGELOG.md
2026-02-24 11:37:21 +00:00

211 lines
12 KiB
Markdown

# Changelog
All notable changes to Chat Switchboard.
## [0.9.4] — 2026-02-24
### Added
- **API key encryption (vault)** — Two-tier AES-256-GCM encryption for stored
API keys. Global/team keys encrypted with env-var-derived key (HKDF-SHA256);
personal BYOK keys encrypted with per-user encryption key (UEK) derived from
password via Argon2id. Admin cannot recover personal keys without user's
passphrase. New `server/crypto/` package: `vault.go`, `cache.go`,
`resolver.go`, `backfill.go` with 11 round-trip tests.
- **UEK lifecycle** — UEK generated on registration, unwrapped on login
(Argon2id → AES-GCM), cached in `sync.Map` for session duration, evicted
with memory zeroing on logout. Pre-migration users auto-initialize vault
on first login.
- **Migration 003_vault.sql** — Adds `encrypted_uek`, `uek_salt`, `uek_nonce`,
`vault_set` to users table. Adds `api_key_enc` (BYTEA), `key_nonce`,
`key_scope` to provider_configs. Backfills `key_scope` from existing scope.
- **Startup backfill** — `BackfillEncryptedKeys()` encrypts plaintext API keys
on first startup with `ENCRYPTION_KEY` set. `EnforceEncryptionKey()` refuses
startup if encrypted keys exist but env var is missing.
- **CI/CD: encryption secret** — `ENCRYPTION_KEY` Gitea secret synced to k8s
`switchboard-encryption` secret. Backend manifest references it as optional
`secretKeyRef`.
### Fixed
- **HTML code blocks render live in chat (XSS)** — When a model's `</think>`
tag directly abutted a code fence (no newline), the fence wasn't recognized
by marked.js, causing raw HTML to render as live DOM elements (canvas games,
styles, etc.). Three-layer fix: (1) DOMPurify switched from permissive
`ADD_TAGS` (default allows canvas, style, form, etc.) to strict
`ALLOWED_TAGS` allowlist of only markdown-produced elements; (2) think-block
placeholders padded with `\n\n` to ensure adjacent fences start on fresh
lines; (3) unclosed code fences auto-closed before `marked.parse` for
streaming protection.
## [0.9.3] — 2026-02-23
### Changed
- **Code blocks: button-driven collapse** — Replaced `<details>` wrapper with
inline collapse/expand toggle button in the code toolbar. Auto-collapses at
>15 lines with a fade mask; toggle available on all blocks >5 lines. User
can always expand/collapse regardless of threshold. Smooth CSS transition
instead of native `<details>` jump.
- **Thinking blocks: always visible** — Thinking blocks are always rendered in
the DOM regardless of the `showThinking` setting. The setting now controls
whether blocks start expanded (`<details open>`) or collapsed. User can
always click to toggle. Setting label updated to "Auto-expand thinking blocks".
### Added
- **Admin default model** — New `default_model` policy in Admin → Settings.
Dropdown populated from enabled models. When a user has no saved selection
(fresh login, cleared browser) or their saved model is no longer available,
the admin default is used before falling back to first visible. Resolution
chain: `localStorage → admin default → first visible`.
- **Reasoning/thinking support for OpenAI-compatible providers** — Models that
send `reasoning_content` in stream deltas (Grok, DeepSeek, etc.) now have
thinking blocks streamed live and persisted as `<think>` tags. Renders as
collapsible thinking blocks in both streaming and history views.
- **Favicon animation during generation** — Browser tab favicon pulses with
cascading dot opacity animation while a completion is in progress, restoring
to the static favicon when done.
- **Tool calls in message history** — Tool call metadata (name, arguments,
result, error status) is now persisted alongside assistant messages in the
database. History view renders tool calls as collapsed `<details>` blocks
showing "🔧 tool_name → done" with expandable input/output JSON.
- **Notes tool: "View note" link** — When a notes tool (`note_create`,
`note_update`, etc.) completes, a "📝 View note" button appears in both
the live streaming tool indicator and the history tool call block. Clicking
opens the Notes panel and navigates directly to the note.
- **Notes panel: Copy button** — "Copy" button added to note read mode,
copies title + content as markdown to clipboard.
- **Brand hover: jitter-free crossfade** — Sidebar brand logo→collapse icon
swap uses opacity crossfade instead of `display: none` toggle, eliminating
layout reflow jitter on hover.
### Fixed
- **Model selector shows unavailable model** — Selector restoration searched
all models including user-hidden ones. Saved selection (localStorage) for a
hidden model like Claude Opus would re-select it on every page load even when
only Grok was visible. Resolution now filters to visible models only.
- **Refresh toast shows wrong count** — "Loaded 33 models" counted all models
including hidden; now shows only visible count ("Loaded 1 model").
- **Tool calls vanish after completion** — Live streaming tool indicators
disappeared when `reloadActivePath()` rebuilt messages from DB. Fixed: the
`getActivePath` query now includes `tool_calls` column, and `PathMessage`
carries the data through to the frontend renderer.
- **Tool result "undefined results" text** — Operator precedence bug in tool
result summary parser caused `undefined results` to display for note_create.
Fixed summary logic to properly branch on title vs count.
- **Regenerate streams at wrong position** — Regen'd response appeared below
the full conversation (appended at bottom) then snapped to correct position
after completion. Fixed: display is now truncated to the parent message
before streaming starts, so the new response streams in-place as a clean
branch. Backend context was already correct (excludes old response).
- **Regenerate loses tools, reasoning, and tool execution** — Regen handler
was a stripped-down copy of the completion handler missing tool definitions,
tool execution loop, reasoning_content forwarding, and tool_calls persistence.
Model lost access to note tools on regen and fell back to generic capabilities.
Refactored: extracted `streamWithToolLoop()` into `stream_loop.go` as the
single canonical streaming implementation. Both `streamCompletion` (normal
chat) and `Regenerate` now call the shared function — only persistence
differs. Future streaming features (new event types, tool capabilities)
automatically apply to all code paths.
- **OpenRouter free models crash** — Free models with nil pricing caused
`pq: invalid input syntax for type json` on catalog sync. Nil pricing now
routes through `ToJSON()` producing `"{}"` instead of nil `[]byte`.
- **API key appears unsaved** — `ListGlobalConfigs` returned raw
`ProviderConfig` structs where `APIKeyEnc` is `json:"-"` (never serialized),
so `has_key` was always `undefined`. Now returns computed `has_key` field.
- **Case-insensitive usernames** — Login, registration, and all user lookups
use `LOWER()` in SQL. All user creation paths normalize to lowercase.
New migration `002_ci_username.sql` adds `LOWER()` unique indexes and
normalizes existing rows.
## [0.9.2] — 2026-02-23
### Added
- **Collapsible code blocks**: Code blocks over 15 lines auto-collapse into
`<details>` with language and line count summary. Language label shown in
top-left corner of all code blocks.
- **HTML preview**: "Preview" button on HTML code blocks opens a sandboxed
iframe (`allow-scripts`, no `allow-same-origin`). Auto-detected for
untagged blocks via heuristic.
- **Token count estimate**: Live token counter below input area showing
approximate tokens and context usage percentage when model has `max_context`.
- **Context length warning**: Dismissable banner above input at 75% (yellow)
and 90% (red) context usage with guidance to start a new chat.
- **Proxy interception detection**: `_parseJSON()` checks Content-Type before
`.json()` on all API paths including streaming. Typed `proxyBlocked` errors
with proxy page title extraction and actionable splash messages.
- **Environment injection**: `window.__ENV__` wired through entrypoint, k8s,
and index.html for dev/test/production gating.
- **Enhanced diagnostics**: `NET:PROXY` log type, Content-Type capture in
fetch interceptor, environment info in export header and state snapshot.
- **Team admin audit scoping**: New `GET /api/v1/teams/:teamId/audit` and
`/audit/actions` endpoints scoped to team members. Activity Log section
in team manage panel with filter dropdown and pagination.
- **New favicon**: Switchboard panel design with provider-colored jacks.
Animated SVG (rotating plugs), 32px PNG, 256px PNG, and ICO.
- **Seed users** (dev/test only): `SEED_USERS=user:pass:role,...` env var
pre-creates active users on startup. Ignored in production. K8s secret
`switchboard-seed-users` wired in backend manifest.
## [0.9.1] — 2026-02-23
### Removed
- **Static known model table**: Deleted `knownModels` map from backend and
`KNOWN_MODELS` from frontend. The same model ID can have different capabilities
depending on the provider (e.g. DeepSeek has tool_calling on OpenRouter but
not on Venice). A hardcoded table can't represent this.
- **Frontend `lookupKnownCaps()`**: Removed client-side capability guessing.
Backend is the sole source of truth via catalog → heuristic chain.
### Changed
- **Resolution chain simplified**: catalog (provider API sync) → heuristic inference.
No intermediate known table. Providers that report capabilities via API are
authoritative; heuristics are best-effort for unsynced models.
- **All providers updated**: OpenAI, OpenRouter, Anthropic, Venice now call
`InferCapabilities()` directly instead of the dead known table lookup.
- **Frontend `resolveCapabilities()`**: Now passes through backend caps as-is.
No client-side merging with a static table.
- [x] EXTENSIONS.md recovered into repo, updated with Appendix A (Custom
Renderers) and Appendix B (Model Roles with utility/embedding/generation slots)
- [x] ROADMAP.md restructured: extension foundation pulled to v0.11.0,
model roles to v0.10.0, dependency graph, TBD replaces post-1.0,
removed v0.8→v0.9 migration (OBE — no public release, no test path)
### Added
- **Heuristic patterns**: Updated to detect qwen3, gpt-5, grok, kimi, minimax,
glm-5, gemma-3 model families. Vision expanded to claude-opus/sonnet (not
just claude-3). Reasoning expanded for thinking, grok, glm patterns.
### Fixed
- **Preset capability pills**: Presets with auto-resolve (no `provider_config_id`)
now inherit base model capabilities via `GetByModelIDAny` catalog fallback.
- **Venice `optimizedForCode`**: Added mapping to `CodeOptimized` capability.
- **CI test stability**: BYOK journey tests use unreachable endpoints so auto-fetch
doesn't race with simulated data injection.
## [0.9.0] — 2026-02-22
### Added
- **Schema consolidation**: 21 migrations collapsed to single `001_initial.sql`
- **Store layer**: All database access through typed interfaces (no raw SQL in handlers)
- **Persona model**: Trust-boundary model replacing old presets; scoped global/team/personal
- **Capabilities resolver**: Three-tier chain — catalog → known table → heuristic inference
- **Three-state model visibility**: enabled / disabled / team-only
- **BYOK auto-fetch**: Creating a personal provider triggers model discovery from provider API
- **User model refresh**: `POST /api-configs/:id/models/fetch` endpoint + UI button
- **Composite model IDs**: `configId:modelId` format prevents cross-provider collisions
- **Audit log foundation**: All admin operations logged with actor, action, resource
- **Journey integration tests**: API-driven test suite replacing fake-data tests
- **Frontend test suite**: 107 tests, 27 suites validating model processing pipeline
- **Live Venice API test**: Proves real BYOK → auto-fetch → models visible flow
### Fixed
- **API key storage**: `json:"-"` tag on `ProviderConfig.APIKeyEnc` silently dropped
keys during admin create/update. Fixed with wrapper structs that bypass the tag.
- **NULL model_default scan**: `scanProviders()` crashed on NULL `model_default` column,
silently hiding all team and BYOK models. Fixed with `sql.NullString`.
- **Nil slice serialization**: Go nil slices serialized as JSON `null` instead of `[]`,
breaking frontend fallback chains. Fixed with `make([]T, 0)`.
- **Frontend error swallowing**: API responses with `errors` field were silently ignored.
### Changed
- Backward-compatible API routes with v0.8 field name aliases
- User model preferences table (`user_model_settings`)