1609 lines
122 KiB
Markdown
1609 lines
122 KiB
Markdown
# Changelog
|
||
|
||
All notable changes to Chat Switchboard.
|
||
|
||
## [0.22.6] — 2026-03-03
|
||
|
||
### Fixed
|
||
- **Split FE/BE deployment: page route proxying.** Frontend container now supports `BACKEND_URL` env var. When set, the entrypoint generates nginx `proxy_pass` blocks for page routes (`/`, `/login`, `/chat/:id`, `/admin/*`, `/editor/*`, `/notes/*`, `/settings/*`) so Go template rendering works in k8s split-image deployments. When unset, behavior is unchanged (SPA fallback). Both `BASE_PATH=""` and `BASE_PATH="/prefix"` variants supported.
|
||
- **CI test timeout.** All `go test` commands now include `-timeout 8m` to prevent indefinite hangs from runner resource contention or race-detector compilation stalls. Previously used Go's 10-minute default with no goroutine dump on timeout.
|
||
|
||
### Removed
|
||
- **`router.js` (322 lines).** Client-side hash router fully replaced by server-side page routes. All `Router.*` call sites in `chat.js` replaced with `history.replaceState()` for URL updates. All `typeof Router` guards in `app.js` removed.
|
||
- **`surfaces.js` (368 lines).** Client-side surface registry replaced by Go template surface architecture. Extension API stubs (`surfaces.register`, `surfaces.activate`, etc.) now log deprecation warnings. `surfaces.getCurrent()` returns `window.__SURFACE__` from server-rendered page data.
|
||
- **`index.html` SPA shell (1,296 lines → 16 lines).** Original monolithic SPA entry point replaced with a minimal redirect page. All real routes now served by Go templates; `index.html` only serves as nginx `try_files` fallback for unknown paths.
|
||
- **`editor-mode.js` old Surfaces paths (~260 lines).** Removed `_register()`, `openDirect()`, `_unregister()`, `_activate()`, `_deactivate()`, `_embedChat()`, `_returnChat()`, and full `check()`/`checkStartup()` implementations. Only `mountServerRendered()` remains as the entry point. `check()` and `checkStartup()` kept as no-ops for backward compat with `projects-ui.js` callers.
|
||
- **SPA-only entrypoint code path.** `docker-entrypoint-fe.sh` now requires `BACKEND_URL` (fails fast if unset). Removed dead `%%BASE_HREF%%`, `%%ENVIRONMENT%%`, `%%BRANDING_JSON%%` injection into index.html. Removed branding config file loading (branding now handled by Go templates).
|
||
|
||
### Changed
|
||
- `docker-entrypoint-fe.sh`: Simplified from 227 → 184 lines. Requires `BACKEND_URL`. Removed SPA-only fallback path.
|
||
- `nginx.conf` (unified container): Consolidated repeated `proxy_set_header` blocks. Uses `$backend` variable. Page routes proxy to Go backend for template rendering.
|
||
- `base.html`: Added `__ENV__` and `__BRANDING__` globals for JS compatibility (previously injected by index.html sed).
|
||
- `server/pages/pages.go`: Added `Environment` field to `PageData`, populated from config.
|
||
- `extensions.js`: `ui.replace()`, `ui.restore()`, and `surfaces.*` API methods now log deprecation warnings instead of calling removed Surfaces system.
|
||
- `app.js`: Removed `Surfaces.init()`, `EditorMode.checkStartup()`, and `Router.init()` calls. Simplified to direct `sessionStorage` chat restore.
|
||
- `chat.js`: Replaced `Router.update()` calls with `history.replaceState()` for URL-bar sync on chat selection/creation.
|
||
- `repl.js`: Removed `surfaces.js` import reference.
|
||
|
||
## [0.22.5] — 2026-03-03
|
||
|
||
### Added
|
||
- **Go template engine.** Server-side HTML rendering via `html/template` with `//go:embed` for compiled-in templates. Custom `FuncMap` with helpers (`roleFilterType`, `toJSON`, `hasPrefix`, `dict`). CSP nonce generation per request. Replaces monolithic `index.html` + client-side DOM construction with composable, server-rendered surfaces.
|
||
- **Surface architecture.** Each page route renders a dedicated surface template that owns its full layout below the classification banner. Surfaces compose from reusable components (`model-select`, `team-select`, `file-upload`) without sharing CSS layout rules. Banner height handled via CSS custom properties (`--banner-top-h`, `--banner-bot-h`, `--surface-h`).
|
||
- **Chat surface.** Server-rendered shell at `/` and `/chat/:chatID`. Go template renders banner + layout + script tags; existing JS builds DOM inside the container. Bridge pattern preserves all current chat functionality.
|
||
- **Admin surface.** Full admin panel at `/admin/:section` with 5 category tabs (People, AI, Routing, System, Monitoring) and section sidebar. Server-rendered pages for providers, models, teams, users, and settings. Hybrid fallback for JS-loaded sections (groups, presets, knowledge, memory, health, capabilities, extensions, storage, usage, audit, stats).
|
||
- **Editor surface.** Server-rendered layout shell at `/editor/:wsId` with proper CSS height ownership. `mountServerRendered()` method on `EditorMode` bypasses the Surfaces registry for Go template mode. Dedicated CSS owns full viewport below banners — no layout collision with other surfaces. **(Fixes bugs #4, #5: editor layout collapse)**
|
||
- **Notes surface.** Server-rendered layout at `/notes/:noteId` with notes-main + notes-assist split. Boot script calls `openNotes()` in standalone mode. Responsive: assist pane hidden on mobile.
|
||
- **Settings surface.** Full-page settings at `/settings/:section` replacing modal-based settings. Left nav with all sections (general, appearance, providers, models, presets, roles, knowledge, memory, notifications, usage). General and Appearance sections server-rendered with form controls; dynamic sections populated by existing JS.
|
||
- **Login page.** Standalone Go template at `/login` with cookie-based auth. Sets `redirect_after_login` cookie for post-login navigation.
|
||
- **`AuthOrRedirect` middleware.** Cookie-based JWT validation for page routes. Reads `sb_token` cookie, redirects to `/login` on missing/invalid token (instead of 401 JSON). `RequireAdminPage()` helper aborts with 403 for non-admin users. Skips auth entirely in unmanaged mode.
|
||
- **Cookie-based auth sync.** `api.js` `saveTokens()` now sets `sb_token` cookie alongside localStorage on every token save/refresh. `clearTokens()` clears the cookie. Bridges API auth (Bearer header) with page auth (cookie).
|
||
- **Reusable `model-select` component.** Go template partial renders `<select>` with models filtered by type. Server passes filtered list; client-side cascade handler re-filters from `window.__PAGE_DATA__.models` on provider change. **(Fixes bug #1: admin model roles missing embedding models)**
|
||
- **Reusable `team-select` component.** Go template partial renders team dropdown with pre-populated options from server data loader. **(Fixes bug #2: routing policy team scope as text field)**
|
||
- **Reusable `file-upload` component.** Go template partial for drop zone + file picker. Used by editor surface.
|
||
- **Editor file upload.** Upload button in editor toolbar + drag-and-drop on file tree with visual feedback. `API.uploadWorkspaceFile()` sends File objects as raw body to existing `WriteFile` handler (binary-safe, respects workspace quota). Auto-opens single uploaded text files. **(Fixes bug #3: unable to upload files to a project)**
|
||
- **Page route data loaders.** Each surface registers a loader that pre-fetches exactly what its template needs: `chatLoader`, `adminLoader` (with section-specific data), `editorLoader`, `notesLoader`, `settingsLoader`. No over-fetching, no client-side fetch waterfall.
|
||
- **Admin provider CRUD.** Server-rendered provider table with add/edit form, type dropdown, sync button. Client-side handlers for create, update, delete, sync operations.
|
||
- **Admin model table.** Server-rendered catalog table with client-side search + type/provider filters.
|
||
- **Admin user management.** Server-rendered user table with search, role edit, enable/disable toggles.
|
||
- **Admin settings page.** Registration policy, permissions, banner config, and system prompt — all server-rendered with save handlers.
|
||
- **`pages.js` client handlers.** Role CRUD, routing policy CRUD, provider/model/team/user management, settings save — all working against existing API endpoints with proper token resolution.
|
||
|
||
### Changed
|
||
- `nginx.conf`: Added page route proxy blocks (`/`, `/login`, `/chat`, `/admin`, `/editor`, `/notes`, `/settings`) before SPA fallback. Static assets still served directly by nginx. Removed `/legacy` proxy block.
|
||
- `server/main.go`: Template engine init, `pages.SetVersion()`, route wiring for all surfaces with auth middleware groups. Removed `/legacy` fallback route.
|
||
- `src/js/api.js`: `saveTokens()` / `clearTokens()` sync `sb_token` cookie. New `uploadWorkspaceFile()` method for binary file upload to workspaces.
|
||
- `src/js/editor-mode.js`: Upload button + hidden file input in header. Drag-and-drop on file tree container with `.drag-over` visual state. `_uploadFiles()` method with text-file auto-open. `mountServerRendered()` for Go template boot path.
|
||
- `src/css/editor-mode.css`: `.drag-over` style for file tree drop target.
|
||
|
||
### Fixed
|
||
- **Bug #1**: Admin model roles now show embedding models. Server-side `roleFilterType()` sets `FilterType="embedding"` for the embedding role; `model-select` component filters by `model_type`.
|
||
- **Bug #2**: Routing policy team scope uses proper dropdown instead of free-text field. `adminLoader` pre-fetches team list; `team-select` component renders options.
|
||
- **Bug #3**: Editor file upload works. Upload button + drag-and-drop + `uploadWorkspaceFile()` API method.
|
||
- **Bug #4**: Editor chat assist pane is functional. Server-rendered layout with dedicated mount points.
|
||
- **Bug #5**: Editor and chat pane no longer squashed. Each surface owns its full viewport height via dedicated CSS — no shared flex container conflicts.
|
||
|
||
### Removed
|
||
- `/legacy` route and nginx proxy block. All page routes are now server-rendered.
|
||
|
||
## [0.22.4] — 2026-03-02
|
||
|
||
### Added
|
||
- **Rate limit tracking.** Provider health windows now track HTTP 429 / rate limit events separately from general errors. New `rate_limit_count` column on `provider_health` table. `RecordRateLimit()` method on health accumulator. Completion handler and stream loop classify `HTTP 429` / `rate limit` / `Too Many Requests` errors automatically.
|
||
- **Auto-disable policy.** Providers that report "down" status for N consecutive hourly windows are automatically deactivated. Configurable via `PROVIDER_AUTO_DISABLE_THRESHOLD` env var (default: 3, set to 0 to disable). `AutoDisabler` interface on health store, `checkAutoDisable()` runs on each flush cycle.
|
||
- **Tool health tracking.** Built-in tools (web_search, url_fetch, etc.) now record success/error/latency to `tool_health` table via the health accumulator. `RecordToolSuccess()` / `RecordToolError()` methods. Both tool execution sites in stream loop instrumented. `ToolHealthWindow` and `ToolHealthSummary` models.
|
||
- **Search provider health tracking.** web_search and url_fetch tool calls are now included in the tool health pipeline with per-invocation latency and error recording.
|
||
- **Project-specific file uploads.** `POST /api/v1/projects/:id/files` (multipart upload) and `GET /api/v1/projects/:id/files` (list). `project_id` column on `attachments` table. Files are stored under `projects/{id}/` prefix. Project access verified via ownership or team membership. Files queued for text extraction if enabled.
|
||
- **Workspace settings UI.** Git configuration section in project settings panel: remote URL, branch, credential selector. Appears when a workspace is bound. Saves via `PATCH /api/v1/workspaces/:id`. `updateWorkspace()` and `listGitCredentials()` API client methods.
|
||
- **PDF/DOCX export via pandoc.** New `POST /api/v1/export` endpoint converts markdown content to PDF or DOCX using pandoc. Editor export dropdown now includes "Export as PDF" and "Export as DOCX" options. Graceful error handling when pandoc is unavailable.
|
||
- **Project files UI.** File list and upload button in project settings panel. Shows file names and sizes. Multi-file upload support.
|
||
- **API client methods.** `projectUploadFile`, `projectListFiles`, `exportDocument`, `updateWorkspace`, `listGitCredentials`.
|
||
|
||
### Changed
|
||
- `health/accumulator.go`: Added `rateLimitCount` to bucket, `RecordRateLimit()`, `RecordToolSuccess()`, `RecordToolError()`, `flushTools()`, `checkAutoDisable()`, `SetAutoDisable()`, `AutoDisabler` interface, `toolBucket` struct. `Store` interface extended with `UpsertToolWindow()`, `ListAllToolCurrent()`.
|
||
- `handlers/completion.go`: `HealthRecorder` interface extended with `RecordRateLimit()`, `RecordToolSuccess()`, `RecordToolError()`. `recordHealth()` classifies HTTP 429 errors.
|
||
- `handlers/stream_loop.go`: Both tool execution sites now record tool health. `recordHealthFn()` classifies rate limit errors.
|
||
- `models/models.go`: `RateLimitCount` field on `ProviderHealthWindow`. `ToolHealthWindow`, `ToolHealthSummary` types. `ErrorCount`, `RateLimitCount`, `TimeoutCount` fields on `ProviderHealthSummary`. `ProjectID` field on `Attachment`.
|
||
- `store/postgres/health.go`: All queries updated for `rate_limit_count`. `UpsertToolWindow()`, `ListAllToolCurrent()`, `DeactivateProvider()` methods.
|
||
- `store/sqlite/health.go`: Full rewrite with rate_limit_count, tool health, and auto-disable support.
|
||
- `store/postgres/attachment.go`: `project_id` in cols, scan, create, and new `GetByProject()`.
|
||
- `store/sqlite/attachment.go`: Same project_id additions.
|
||
- `store/interfaces.go`: `GetByProject()` on `AttachmentStore`.
|
||
- `config/config.go`: `ProviderAutoDisableThreshold` field + env var loading.
|
||
- `main.go`: SQLite health store branching, auto-disable wiring, export handler route, project file routes.
|
||
- `handlers/health_admin.go`: Health summary includes error_count, rate_limit_count, timeout_count.
|
||
- `src/js/ui-admin.js`: Health dashboard cards show "Rate Limits" count with warning color.
|
||
- `src/js/projects-ui.js`: Git settings section, project files section, file upload handler.
|
||
- `src/js/editor-mode.js`: PDF and DOCX export menu items and handler.
|
||
- `src/js/api.js`: 5 new API client methods.
|
||
|
||
### Database
|
||
- Migration 015 (Postgres) / 014 (SQLite): `rate_limit_count` column on `provider_health`, `tool_health` table, `project_id` column on `attachments`.
|
||
|
||
## [0.22.3] — 2026-03-02
|
||
|
||
### Added
|
||
- **Provider admin UI: "Routing" category.** New admin category with three sections: Health, Routing, and Capabilities. Accessible from admin panel sidebar navigation.
|
||
- **Health dashboard.** Admin section showing per-provider status badges, request counts, error rates, average/max latency, timeout counts, and last error messages. Refresh button for live updates. CSS grid layout with responsive cards.
|
||
- **Routing policy builder.** Full CRUD UI for routing policies: create/edit form with name, priority, type (provider_prefer/team_route/cost_limit/model_alias), scope (global/team), team ID, JSON config editor, active toggle. List view with edit/enable/disable/delete actions.
|
||
- **Routing dry-run test panel.** Input model + user ID, evaluates against all active policies with live health status, displays ranked candidates with status badges and selection reasoning.
|
||
- **Capability override viewer.** Table listing all model capability overrides with model ID, provider config, field, value, and delete button. Explains auto-detection fallback when no overrides exist.
|
||
- **`provider_status` on models/enabled response.** `GET /api/v1/models/enabled` now includes `provider_status` field ("healthy"/"degraded"/"down"/"unknown") per model from live health data.
|
||
- **`ModelHandler` with health enrichment.** Extracted model listing into dedicated `handlers/capabilities.go` with `SetHealthStore()` for health status injection. Builds health map from current hourly windows.
|
||
- **`GET /api/v1/admin/capability-overrides` endpoint.** Returns all capability overrides across all models (admin view).
|
||
- **API client methods.** `adminGetAllProviderHealth`, `adminListRoutingPolicies`, `adminGetRoutingPolicy`, `adminCreateRoutingPolicy`, `adminUpdateRoutingPolicy`, `adminDeleteRoutingPolicy`, `adminTestRouting`, `adminListCapabilityOverrides`, `adminDeleteModelCapability`.
|
||
|
||
### Changed
|
||
- `src/js/ui-admin.js`: New "routing" admin category with health/routing/capabilities sections. `ADMIN_SECTIONS`, `ADMIN_LABELS`, `ADMIN_LOADERS` updated. Added `loadAdminHealth()`, `loadAdminRouting()`, `loadAdminCapabilities()`, `_showRoutingForm()`, `_toggleRoutingPolicy()`, `_deleteRoutingPolicy()`, `_runRoutingTest()`, `_deleteCapOverride()`.
|
||
- `src/js/api.js`: 9 new API client methods for health, routing, and capability admin endpoints.
|
||
- `src/index.html`: New admin category button for "Routing". Three new `admin-section-content` divs: `adminHealthTab`, `adminRoutingTab`, `adminCapabilitiesTab`.
|
||
- `src/css/styles.css`: `.admin-health-grid` and `.admin-health-card` styles.
|
||
- `models/models.go`: `ProviderStatus` field on `UserModel` struct.
|
||
- `handlers/capabilities.go`: Extracted `ModelHandler` from inline handlers. `ListEnabledModels` enriches response with provider health status. `ResolveModelCaps` canonical capability resolver.
|
||
- `main.go`: `ModelHandler` created with `SetHealthStore()` wiring. Capability override admin routes registered.
|
||
|
||
## [0.22.2] — 2026-03-02
|
||
|
||
### Added
|
||
- **Routing policy engine.** Rules-based request routing evaluated between "what model was requested" and "which provider serves it." `routing/` package with evaluator, fallback runner, and type-safe policy model.
|
||
- **`provider_prefer`**: Ordered list of preferred provider configs — fallback to others if preferred are unavailable.
|
||
- **`team_route`**: Restrict a team to only use specific provider configs (team-scoped access control).
|
||
- **`cost_limit`**: Filter out providers above a heuristic cost threshold per request.
|
||
- **`model_alias`**: Map alias names ("fast", "smart") to specific provider+model pairs.
|
||
- **Health-aware routing.** Candidates sorted by health status (healthy > degraded > unknown > down). `skip_down` flag removes down providers from consideration when healthy alternatives exist. All-down graceful degradation: keeps candidates rather than returning empty.
|
||
- **Fallback runner.** `routing.RunWithFallback()` tries candidates in order with configurable retry depth. Logs each failure and tries the next candidate. Returns the winning candidate and updated decision with fallback depth.
|
||
- **`routing_policies` table.** New migration (Postgres 014, SQLite 013) with scope (global/team), priority ordering, JSONB config, and active flag. Index on `(is_active, priority)` for efficient policy loading.
|
||
- **Routing policy admin CRUD.** Full REST API for policy management:
|
||
- `GET /api/v1/admin/routing/policies` — list all policies
|
||
- `GET /api/v1/admin/routing/policies/:id` — get single policy
|
||
- `POST /api/v1/admin/routing/policies` — create policy (validates type, scope, team_id)
|
||
- `PUT /api/v1/admin/routing/policies/:id` — update policy
|
||
- `DELETE /api/v1/admin/routing/policies/:id` — delete policy
|
||
- **Dry-run routing test.** `POST /api/v1/admin/routing/test` accepts model + user_id + team_ids, evaluates active policies against all global provider configs with live health status, returns ranked candidates and the routing decision.
|
||
- **`X-Switchboard-Provider` response header.** Every completion response includes `providerID/configID` for routing observability.
|
||
- **`routing_decision` column on `usage_log`.** JSONB field for recording which policy matched, fallback depth, and total candidates evaluated.
|
||
- **`RoutingPolicyStore` interface** with Postgres and SQLite implementations. Supports Create, Update, Delete, GetByID, ListActive, ListAll, ListForTeam.
|
||
- **12 evaluator tests.** Coverage: no policies, provider_prefer ordering, team_route filtering (including wrong-team no-op), model_alias rewrite, health skip-down, health sorting, all-down graceful degradation, priority ordering, empty candidates, inactive policy skip.
|
||
|
||
### Changed
|
||
- `models/models.go`: Added `RoutingPolicy` struct and `RoutingDecision` field on `UsageEntry`.
|
||
- `store/interfaces.go`: Added `RoutingPolicyStore` interface and `RoutingPolicies` field on `Stores`.
|
||
- `store/postgres/stores.go`, `store/sqlite/stores.go`: Wired `RoutingPolicyStore` into factory.
|
||
- `handlers/completion.go`: Added `router`, `healthStore` fields. `HealthStatusQuerier` interface. `evaluateRouting()` method builds candidates from accessible configs, queries health status, runs evaluator, reloads credentials on config switch. `X-Switchboard-Provider`, `X-Switchboard-Route`, `X-Switchboard-Fallback` response headers.
|
||
- `main.go`: Creates `routing.Evaluator`, wires into completion handler via `SetRoutingEvaluator()` and `SetHealthStore()`. Registers 6 admin routing routes.
|
||
|
||
### Database
|
||
- Postgres: `server/database/migrations/014_v0222_routing.sql`
|
||
- SQLite: `server/database/migrations/sqlite/013_v0222_routing.sql`
|
||
|
||
## [0.22.1] — 2026-03-02
|
||
|
||
### Added
|
||
- **Provider profile schemas.** Each provider type declares its configurable settings with types, defaults, validation constraints, and dependency rules. Built-in schemas for openai, anthropic, venice, openrouter. Unknown types fall back to the openai schema. `providers/profile.go`.
|
||
- **Provider hooks (pre-request / post-stream).** Declarative request/response transforms driven by `provider_configs.settings` JSONB. Replaces any need for hardcoded provider switch statements in the completion path. `providers/hooks.go`.
|
||
- **OpenAI:** `system_prompt_prefix`, `frequency_penalty`, `presence_penalty` → injected into ExtraBody.
|
||
- **Anthropic:** `extended_thinking` + `thinking_budget` → injects `thinking` config into wire JSON, sets `anthropic-beta` header, clears temperature.
|
||
- **Venice:** `enable_thinking`, `enable_web_search`, `include_venice_system_prompt` → builds `venice_parameters` in ExtraBody.
|
||
- **OpenRouter:** `route`, `require_parameters` → serialized into `X-Provider-Preferences` header.
|
||
- **`ExtraBody` on `CompletionRequest`.** Provider hooks write arbitrary key-value pairs that get merged into the wire-format JSON via `mergeExtraBody()` in both OpenAI and Anthropic `doRequest` methods. Keeps the canonical request type clean while supporting provider-specific extensions.
|
||
- **Anthropic extended thinking stream support.** `content_block_delta` with `type=thinking_delta` now routes to the `Reasoning` field on `StreamEvent`, matching the existing `reasoning_content` path used by OpenAI-compatible providers.
|
||
- **Provider type registry.** `providers.RegisterType()` combines provider implementation + metadata (name, description, default endpoint, profile schema). `providers.ListTypes()` returns all registered types.
|
||
- **`GET /api/v1/admin/provider-types` endpoint.** Returns metadata and profile schemas for all registered provider types. Used by admin UI to render provider creation forms and show available settings per type.
|
||
- **Preset setting overrides.** `MergePresetSettings()` merges persona-level overrides onto provider-level settings, respecting `ProviderOnly` fields that cannot be changed at the preset level.
|
||
- **Comprehensive tests.** `providers/hooks_test.go`: 17 tests covering all four hook implementations, profile schemas, GetHooks, MergePresetSettings, mergeExtraBody, nil safety.
|
||
|
||
### Changed
|
||
- `providers/registry.go`: `Init()` now uses `RegisterType()` with full metadata instead of bare `Register()`. `ProviderTypeMeta` struct with ID, name, description, default endpoint, profile schema.
|
||
- `providers/provider.go`: `CompletionRequest.ExtraBody` field (json:"-"). `mergeExtraBody()` helper function. `Model` struct unchanged.
|
||
- `providers/openai.go`: `doRequest()` merges ExtraBody into wire JSON before HTTP dispatch.
|
||
- `providers/anthropic.go`: `doRequest()` merges ExtraBody + sets `anthropic-beta` header when thinking is enabled. Stream processing handles `thinking_delta` content block deltas. Wire type gains `Thinking` field.
|
||
- `handlers/completion.go`: `PreRequest` hook invoked at all three dispatch paths (multi-model, streaming, sync).
|
||
- `handlers/stream_loop.go`: `PostStreamEvent` hook invoked after each streaming event in both `streamWithToolLoop` and `streamModelResponse`.
|
||
- `handlers/health_admin.go`: Added `GetProviderTypes` handler.
|
||
- `main.go`: `GET /api/v1/admin/provider-types` route registered.
|
||
|
||
## [0.22.0] — 2026-03-02
|
||
|
||
### Added
|
||
- **Provider health tracking.** In-memory accumulator records success/error/timeout per provider config, flushes hourly buckets to `provider_health` table every 60s. Status derivation: healthy (<5% errors), degraded (5–25%), down (>25%). Background prune removes data older than 7 days.
|
||
- **Health admin endpoints.** `GET /api/v1/admin/providers/:id/health` returns per-provider summary + hourly windows. `GET /api/v1/admin/providers/health` returns current-hour status for all providers.
|
||
- **Capability admin overrides.** `capability_overrides` table supports per-provider or global overrides for any model capability field. Three-tier resolution chain: catalog → heuristic → admin override (highest priority).
|
||
- **Capability override endpoints.** `GET /api/v1/admin/models/:id/capabilities` with source annotations (catalog/heuristic/override). `PUT` to set, `DELETE` to remove, `GET /api/v1/admin/capability-overrides` to list all.
|
||
- **Workspace pane layout.** Frontend architecture replaces `.chat-area` + `.side-panel` with `.workspace` flex container. Primary and secondary panes are independent with drag-resize handle between them. Surfaces declare layout preferences via `primary`, `secondary`, `secondaryOpts` fields.
|
||
|
||
### Fixed
|
||
- **`scanJSON` driver buffer aliasing** (v0.21.7 bugfix). `safe_json.go` now copies the `[]byte` from the database driver instead of aliasing the slice header. Fixes intermittent `invalid character` errors on multi-row JSON responses caused by driver buffer reuse between `rows.Next()` calls.
|
||
- **`CreateChannel` RETURNING path** now uses `scanJSON(&ch.Settings)` instead of bare `&ch.Settings` scan.
|
||
- **`UpdateChannel` settings validation** — `json.Valid()` check before JSONB merge, returns 400 on invalid JSON instead of storing corrupt data.
|
||
|
||
### Changed
|
||
- `capabilities/intrinsic.go`: `ResolveIntrinsic()` takes `[]models.CapabilityOverride` as third parameter. `applyOverrides()` flips bool fields and sets int fields.
|
||
- `handlers/completion.go`: `HealthRecorder` interface, `recordHealth()` called after every `ChatCompletion` call.
|
||
- `handlers/stream_loop.go`: `streamWithToolLoop` and `streamModelResponse` accept `configID` + `HealthRecorder` params. Health recorded on error, timeout, and success paths.
|
||
- `store/interfaces.go`: `CapOverrides CapabilityOverrideStore` added to `Stores` struct.
|
||
- `models/models.go`: `ProviderStatus`, `ProviderHealthWindow`, `ProviderHealthSummary`, `CapabilityOverride` types.
|
||
- `main.go`: Health accumulator lifecycle (start/stop/prune), admin route registration, startup log includes health status.
|
||
- `panels.js`: Simplified to single `_active` panel in workspace secondary pane. Removed `_dualMode`, `_splitRatio`, `_secondary`.
|
||
- `surfaces.js`: Layout declarations (`primary`, `secondary`, `secondaryOpts`) in surface registration.
|
||
- `editor-mode.js`: Layout declarations for editor surface.
|
||
- `index.html`: Workspace container structure, removed dual-view split button.
|
||
- `styles.css`: Workspace pane CSS grid, handle, responsive rules.
|
||
- `app.js`: `_initWorkspaceResize()` replaces `_initSidePanelResize()` + `_initDualDivider()`.
|
||
- `ui-settings.js`: Zoom selector targets updated class names.
|
||
|
||
### Database
|
||
- Migration 013 (Postgres) / 012 (SQLite): `provider_health` table (hourly bucketed metrics), `capability_overrides` table (admin corrections).
|
||
|
||
## [0.21.6] — 2026-03-01
|
||
|
||
### Added
|
||
- **Editor surface: document features merged in.** Article mode (outline, export, word count, focus mode) folded into the single Editor surface rather than shipping as a separate surface. One surface, two concerns: code files get tabs + tree, text files get word count + export. Chat panel on the right with show/hide toggle.
|
||
- **New file button** — Visible in both the file tree header (+ icon) and the editor toolbar ("+ New"). Creates files with smart defaults (markdown files get `# Title` scaffold).
|
||
- **Export dropdown** — Download file, Export as HTML (via marked.js + DOMPurify), Copy to clipboard. Available from editor toolbar for any open file.
|
||
- **Chat panel toggle** — Show/hide the right-side AI chat pane. Editor left pane expands to fill when chat is hidden. Toggle button in toolbar with active state indicator.
|
||
- **Word count + reading time** in status bar for text files (markdown, txt, rst, adoc). ~230 wpm calculation.
|
||
- **Save indicator** in status bar — shows "● Modified" with warning color for unsaved files.
|
||
- **Git status indicators in editor file tree** (closing v0.21.5 deferred) — Tree rows show M/A/U badges with color coding (modified=yellow, added=green, untracked=italic gray, deleted=red strikethrough).
|
||
- **Auto-save on tab/mode switch in editor surface** (closing v0.21.5 deferred) — Modified files auto-save when switching tabs or deactivating the editor surface.
|
||
- **Ctrl+Shift+F workspace search** (closing v0.21.5 deferred) — Maps to Quick Open file finder.
|
||
- **`SEED_PROVIDERS` env var** — Auto-creates global providers on startup from CSV format `provider:api_key[:name]`. Supports openai, anthropic, openrouter, venice, mistral, groq, together, fireworks, deepseek, perplexity with auto-filled endpoints. Idempotent. Blocked in production.
|
||
- **Mode selector labels** — Surface buttons in sidebar show icon + label text ("Chat", "Editor"). Labels hide when sidebar collapsed.
|
||
- **Hash Router (`router.js`)** — URL-driven direct-to-surface navigation:
|
||
- `#/chat` → default chat view
|
||
- `#/chat/ch_abc123` → open specific chat (bookmarkable)
|
||
- `#/editor` → editor surface (auto-picks workspace from project, or shows picker)
|
||
- `#/editor/ws_abc123` → editor with specific workspace
|
||
Browser back/forward works. Replaces `sessionStorage` chat restore. Workspace picker overlay when navigating without a workspace. Auto-selects if only one exists.
|
||
- **`openDirect(wsId)`** method on EditorMode — bypasses `check()` flow, registers surface directly with a given workspace ID. Used by Router for hash-based navigation.
|
||
|
||
### Fixed
|
||
- **`chat.switched` / `chat.created` events never emitted** — `selectChat()`, `newChat()`, and `sendMessage()` now emit these events. This was blocking EditorMode from detecting workspace bindings on channel switch.
|
||
- **`workspace_id` missing from channel responses** — Added to all SELECT/RETURNING/Scan paths for both SQLite and Postgres. Frontend chat objects now include `workspace_id`.
|
||
- **EditorMode.check() optimization** — Checks local `App.chats` data before API calls.
|
||
- **`defaultEndpoints` redeclared** — Renamed to `seedDefaultEndpoints` in `seed_providers.go` to avoid collision with `live_provider_test.go`.
|
||
|
||
### Removed
|
||
- **Article surface (`article-mode.js`, `article-mode.css`)** — Merged into Editor. Document features (export, word count, focus mode) are now part of the unified Editor surface. Article-specific AI tools (suggest_outline, expand_section) deferred to v0.22+ as editor extensions.
|
||
|
||
### Changed
|
||
- `editor-mode.js`: Rebuilt header with New/Export/ChatToggle buttons. File tree has header with + button. Chat panel show/hide. Word count + save indicator in status bar. `_createNewFile()`, `_exportActiveFile()`, `_toggleChat()` methods. `openDirect()` for Router.
|
||
- `editor-mode.css`: Header labels, export dropdown, chat toggle, file tree header, save indicator, word count styles.
|
||
- `chat.js`: Event emissions, `workspace_id` in chat objects, hash sync via `Router.update()`.
|
||
- `app.js`: Router initialization replaces sessionStorage restore.
|
||
- `projects-ui.js`: Project list mapping includes `workspace_id`.
|
||
- `channels.go`: `workspace_id` in all query/scan paths.
|
||
- `surfaces.js`: Updated comment (removed article reference).
|
||
- `styles.css`: Mode selector layout, router picker overlay.
|
||
- `index.html`: Removed `article-mode.css`/`article-mode.js`, added `router.js`.
|
||
- `config.go`: `SeedProviders` field.
|
||
- `main.go`: `SeedProviders()` call.
|
||
- `seed_providers.go`: `seedDefaultEndpoints` (renamed from `defaultEndpoints`).
|
||
- `ROADMAP.md`: Article→Editor merge documented. Go template pages added under v0.25.0.
|
||
|
||
## [0.21.5] — 2026-03-01
|
||
|
||
### Added
|
||
- **Editor Surface (Development Mode).** IDE-like experience built as a v0.21.3 surface consuming workspace primitives. Activates automatically when a channel or project has a bound workspace. Three-pane layout: file tree (sidebar), code editor (CM6), and AI chat panel with resizable split.
|
||
- **`editor-mode.js`** — Full editor surface module. File tree backed by `workspace_ls` API with nested directory expansion, file icons by extension, and context menus. Tab bar with modified indicators, close buttons, and multi-file editing. CM6 integration with language auto-detection (20+ languages) and textarea fallback when CM6 bundle unavailable.
|
||
- **Split pane layout** with draggable resize handle. Editor (left) and AI chat (right) share the main region. Chat DOM borrowed from Surfaces saved fragments — real conversation history and input preserved across mode switches.
|
||
- **Quick Open** (`Ctrl/Cmd+P`): Fuzzy file finder overlay using workspace file index. `Ctrl/Cmd+S` save, `Ctrl/Cmd+W` close tab.
|
||
- **Live editor updates**: When AI tool calls modify workspace files (`workspace_write`, `workspace_patch`), the server emits `workspace.file.changed` via WebSocket. If the file is open and unmodified in the editor, content refreshes automatically.
|
||
- **Status bar**: Current file path, language mode, git branch display.
|
||
- **`editor-mode.css`**: Complete styling for all editor components with dark theme support, mobile responsive (stacked layout at ≤768px).
|
||
- **Workspace API methods** on frontend `API` module: `getWorkspace`, `listWorkspaceFiles`, `readWorkspaceFile`, `writeWorkspaceFile`, `deleteWorkspaceFile`, `mkdirWorkspace`, `getWorkspaceGitStatus`, `getWorkspaceGitBranches`.
|
||
- **`Surfaces.getSavedFragment()` / `putSavedFragment()`** — Cross-surface DOM sharing. Allows the editor surface to borrow chat DOM into its split pane without cloning.
|
||
- **`workspace.file.` event routing** added to server event route table (`DirToClient`).
|
||
- **Workspace management UI**: Create, list, and bind workspaces from the frontend. Project settings panel gains a Workspace section with dropdown picker and "New" button. Chat context menu gains "Set workspace…" option for per-channel override. Both paths support creating new workspaces inline.
|
||
- **`GET /workspaces`** — List all workspaces accessible to the current user (user-owned + team-owned).
|
||
- **`workspace_id` in channel API**: `updateChannelRequest` accepts `workspace_id` for binding/unbinding. `channelResponse` and both list/get queries now include `workspace_id`.
|
||
|
||
### Changed
|
||
- `stream_loop.go`: Emits `workspace.file.changed` event after successful `workspace_write`/`workspace_patch` tool calls.
|
||
- `index.html`: Added `editor-mode.css` stylesheet and `editor-mode.js` script.
|
||
- `channels.go`: `channelResponse` includes `workspace_id`, list/get queries select it, update accepts it.
|
||
- `workspaces.go`: Added `List` handler for `GET /workspaces`.
|
||
- `main.go`: Added `GET /workspaces` route.
|
||
- `projects-ui.js`: Workspace section in project panel, workspace picker in chat context menu.
|
||
- `styles.css`: Workspace row layout in project panel.
|
||
|
||
## [0.21.4] — 2026-03-01
|
||
|
||
### Added
|
||
- **Git Integration.** Workspaces can now track a git remote. Operations use `os/exec` calling the `git` binary (no CGO, no Go git libraries). Credentials are vault-encrypted (AES-256-GCM) using the same pattern as BYOK provider configs.
|
||
- **`git_credentials` table.** Postgres + SQLite migrations. Stores encrypted PAT tokens, HTTP basic auth, and SSH private keys. Columns: id, user_id, name, auth_type, encrypted_data, nonce, created_at.
|
||
- **Workspace git columns.** `git_remote_url`, `git_branch`, `git_credential_id`, `git_last_sync` added to `workspaces` table via migration.
|
||
- **`workspace.GitOps`** — exec-based git operations: Clone, Pull, Push, Status, Diff, Commit, Log, BranchList, Checkout. Credential injection via temporary `GIT_ASKPASS` scripts (PAT), `.git-credentials` files (basic auth), or `GIT_SSH_COMMAND` (SSH keys). Temp files securely wiped after each operation.
|
||
- **Post-operation hooks.** Clone, pull, and checkout automatically trigger workspace reconcile (filesystem → DB sync) and re-index changed files through the v0.21.2 indexing pipeline.
|
||
- **5 git tools** for LLM use: `git_status`, `git_diff`, `git_commit`, `git_log`, `git_branch`. Filtered out when no workspace is bound. Each validates git remote configuration before execution.
|
||
- **9 git API endpoints:** `POST clone`, `POST pull`, `POST push`, `GET status`, `GET diff`, `POST commit`, `GET log`, `GET branches`, `POST checkout` — all under `/api/v1/workspaces/:id/git/`.
|
||
- **3 credential endpoints:** `POST /api/v1/git-credentials` (create), `GET /api/v1/git-credentials` (list user-scoped summaries), `DELETE /api/v1/git-credentials/:id` (delete). Encrypted data never exposed in API responses.
|
||
- **`GitCredentialStore` interface** + Postgres/SQLite implementations. Create, GetByID, ListByUser, Delete with user-scoped authorization.
|
||
- **Security.** `file://` and local path URLs rejected in clone. Error output sanitized to strip credential-containing lines. `GIT_TERMINAL_PROMPT=0` set on all operations to prevent interactive prompts.
|
||
|
||
### Changed
|
||
- `WorkspaceStore` interface: added `SetGitLastSync()` method + both implementations.
|
||
- `models.Workspace`: added git tracking fields. `models.WorkspacePatch`: added git patch fields.
|
||
- Completion handler: git tools disabled alongside workspace tools when no workspace bound.
|
||
- `store.Stores` struct: added `GitCredentials` field.
|
||
- Postgres/SQLite `NewStores()`: wires `GitCredentialStore`.
|
||
|
||
## [0.21.3] — 2026-03-01
|
||
|
||
### Added
|
||
- **Surface Registry (`surfaces.js`).** Mode-switching system that allows extensions to register "surfaces" (UI modes) that take over named regions of the interface. DOM nodes are detached and preserved in `DocumentFragment`s during mode switches — not destroyed — ensuring CM6 editor instances and other stateful components survive transitions.
|
||
- **Surface region attributes.** Four `data-surface-region` attributes on `index.html` containers: `surface-header` (model bar), `surface-main` (chat messages), `surface-footer` (input area), `sidebar-content` (search + chat history). Extensions swap these regions via `ctx.ui.replace()` / `ctx.ui.restore()`.
|
||
- **Mode selector.** Appears in the sidebar (`#modeSelectorWrap`) when ≥1 extension surface is registered beyond the default chat. Shows icon buttons for each mode with active state highlighting.
|
||
- **Extension context API.** `ctx.surfaces.register()`, `ctx.surfaces.unregister()`, `ctx.surfaces.activate()`, `ctx.surfaces.getCurrent()` on the scoped extension context. `ctx.ui.replace()` and `ctx.ui.restore()` for region DOM management.
|
||
- **Surface events.** `surface.activated`, `surface.deactivated`, `surface.registered`, `surface.unregistered` on the EventBus (all `localOnly`).
|
||
- **REPL console (`repl.js`).** Fourth tab in the debug modal (Ctrl+Shift+L). `AsyncFunction` wrapper for top-level `await`. Injected globals: `API`, `Events`, `Extensions`, `Surfaces`, `DebugLog`, `Panels`, `UI`, plus `$()`, `$$()`, `sleep()` helpers. Features: collapsible JSON pretty-printing, red errors with expandable stack traces, command history via ↑/↓ (persisted to sessionStorage), tab-completion on live object graphs and EventBus labels, multi-line input (Shift+Enter), admin-gated (admin role OR `?debug=1` URL param).
|
||
- **CSS.** Mode selector styles (`.mode-selector`, `.mode-btn`, active/hover states). REPL styles (output formatting, value-type color coding, collapsible JSON, input prompt).
|
||
|
||
### Changed
|
||
- `extensions.js` context builder: added `ctx.surfaces` namespace and `ctx.ui.replace()` / `ctx.ui.restore()` methods.
|
||
- `app.js` startup: initializes `Surfaces.init()` before `Extensions.loadAll()`, and `REPL.init()` after auth confirmation.
|
||
- `index.html`: added `data-surface-region` attributes, mode selector container, REPL tab in debug modal, script tags for `surfaces.js` and `repl.js`.
|
||
- `EXTENSIONS.md` §6: updated with implementation details, actual API signatures, and event catalog.
|
||
|
||
## [0.21.2] — 2026-03-01
|
||
|
||
### Added
|
||
- **Workspace Indexing Pipeline.** Background text chunking and embedding for workspace files, reusing the existing `knowledge.SplitText` chunker and `knowledge.Embedder` for vector generation. Indexable file types include 40+ source code extensions (Go, Rust, Python, TypeScript, Perl, etc.) plus all `text/*` MIME types. Code-aware chunking uses 1500-character chunks with function/class/type boundary separators (`\n\nfunc `, `\n\nfn `, `\n\nclass `, `\n\ndef `, `\n\ntype `, `\n\nsub `, `\n\npub `, `\n\nimpl `), falling back to paragraph and line breaks.
|
||
- **Content-addressed skip.** SHA256 computed during `WriteFile` is compared against the prior hash before triggering re-indexing. Unchanged files skip the chunk + embed pipeline entirely, making archive extracts and bulk writes efficient.
|
||
- **`workspace_chunks` table.** New table (Postgres + SQLite migrations) storing text chunks with vector embeddings per workspace file. Postgres uses sequential scan with pgvector `<=>` cosine operator (no IVFFlat/HNSW index — 3072-dim embeddings exceed the 2000-dim index limit; sequential scan is adequate at workspace scale). SQLite uses app-level cosine in Go. Columns: workspace_id, file_id, chunk_index, content, token_count, embedding, metadata (JSONB with line_start, language).
|
||
- **`workspace_files` additions.** `index_status` column (`pending`, `indexing`, `ready`, `error`, `skipped`) and `chunk_count` for tracking indexing progress per file. `workspaces` table gains `indexing_enabled` boolean (default true).
|
||
- **`WorkspaceStore` chunk methods.** Four new store interface methods implemented for both Postgres and SQLite: `InsertChunks` (batch insert with pgvector cast or JSON text), `DeleteChunksByFile`, `SimilaritySearch` (pgvector `<=>` operator or app-level cosine), `UpdateFileIndexStatus`.
|
||
- **`workspace.Indexer`.** Background indexing orchestrator with configurable concurrency semaphore. Single-file indexing (`IndexFile`) triggered on `WriteFile` when content changes. Batch indexing (`IndexBatch`) triggered after `ExtractArchive`. Async goroutines with 5-minute timeout per file. Embedding usage tracked via `Embedder.LogUsage`.
|
||
- **`workspace_search` tool.** Semantic search across workspace files via natural language query. Embeds the query, runs cosine similarity against workspace chunks, returns file paths, content snippets, relevance scores, and approximate line numbers. Optional `file_pattern` glob filtering (e.g. `*.go`, `src/*.ts`) applied post-search. Top-K capped at 20.
|
||
- **Indexer integration.** `workspace.FS.SetIndexer()` hooks the indexer into the write path. `WriteFile` triggers single-file indexing on content change. `ExtractArchive` triggers batch indexing for all extracted files. Both use the workspace owner's identity for embedding provider resolution.
|
||
- **Configuration.** `WORKSPACE_INDEXING_ENABLED` env var (global kill switch, default true). `WORKSPACE_INDEX_CONCURRENCY` env var (default 2). Per-workspace `indexing_enabled` toggle via `WorkspacePatch`. Indexing gracefully degrades when no embedding role is configured.
|
||
- **Index status endpoint.** `GET /api/v1/workspaces/:id/index-status` returns aggregated indexing progress: per-status file counts (pending, indexing, ready, error, skipped), total chunk count, and workspace indexing_enabled flag.
|
||
|
||
### Changed
|
||
- `workspace_files` scanner updated in both Postgres and SQLite stores to include `index_status` and `chunk_count` columns.
|
||
- `workspaces` scanner updated to include `indexing_enabled` column.
|
||
- `WorkspacePatch` model extended with `IndexingEnabled` field.
|
||
- `WorkspaceToolNames()` now includes `workspace_search` in the disabled set when no workspace is bound.
|
||
- `RegisterWorkspaceSearchTool()` added for late registration of the search tool (requires embedder).
|
||
|
||
## [0.21.1] — 2026-03-01
|
||
|
||
### Added
|
||
- **Workspace Tools.** Six AI-callable tools in the `workspace` category: `workspace_ls` (list files with content type/size), `workspace_read` (text content with 50KB soft limit, binary detection), `workspace_write` (create/overwrite with auto-mkdir), `workspace_rm` (delete with recursive guard), `workspace_mv` (move/rename with auto-mkdir), `workspace_patch` (find/replace operations, each match must be unique, 1MB file limit). Late registration via `RegisterWorkspaceTools()` after stores + FS init. `WorkspaceToolNames()` returns the full list for filtering.
|
||
- **Channel Workspace Binding.** `channels.workspace_id` nullable FK column (Postgres + SQLite migration 010/009). Channels with a bound workspace get workspace tools auto-injected into the completion tool set.
|
||
- **Project Workspace Binding.** `projects.workspace_id` nullable FK column. Projects can bind a workspace shared by all channels in the project.
|
||
- **Workspace Resolution Chain.** `ChannelStore.ResolveWorkspaceID()` resolves the effective workspace for a channel: channel `workspace_id` takes priority, falling back to the project's `workspace_id` via `project_channels` join. Both Postgres and SQLite implementations. Returns empty string when no workspace is bound.
|
||
- **Tool Injection in Completion Handler.** `buildToolDefs()` accepts `workspaceID` parameter; when empty, all workspace tool names are added to the disabled set. `ExecutionContext.WorkspaceID` field carries the resolved workspace ID through tool execution and streaming.
|
||
- **Workspace resolution wired into streaming.** Both `CompletionHandler` and `streamWithToolLoop` resolve the workspace ID from the channel and pass it through to tool execution contexts.
|
||
|
||
### Deferred
|
||
- Frontend workspace UI (channel settings toggle, project Files tab, chat bar workspace indicator) deferred to a future release.
|
||
- Archive-to-workspace upload flow (detect archive MIME + workspace binding → extract to workspace) deferred.
|
||
|
||
## [0.21.0] — 2026-03-01
|
||
|
||
### Added
|
||
- **Workspace Storage Primitive.** Platform-level file storage bound to users, projects, channels, or teams via polymorphic owner model. Dual-layer architecture: PVC filesystem (source of truth) with DB metadata index (queryable cache). Workspaces support configurable quotas, status lifecycle (active/archived/deleting), and owner-based authorization inheritance.
|
||
- **Workspace data model.** Two new tables: `workspaces` (polymorphic owner_type/owner_id, root_path, max_bytes quota, status) and `workspace_files` (path, MIME type, size, sha256, is_directory). Unique index on (workspace_id, path) enables upsert-on-conflict for file metadata sync. Migrations for both Postgres and SQLite.
|
||
- **`workspace.FS` package.** Filesystem operations layer with security-first design: atomic writes (temp file + rename with SHA256 computed via tee reader), path traversal guards (cleanPath normalization + absPath containment validation), symlink rejection on write targets. Operations: ReadFile, WriteFile, DeleteFile (with recursive guard), Mkdir, Stat, ListDir, Tree, Reconcile (filesystem→DB drift sync).
|
||
- **Archive operations.** Extract zip and tar.gz archives into workspaces with bomb protection: 10K file limit, 100MB single file cap, workspace quota enforcement during extraction. Common-prefix stripping handles GitHub-style `project-name/` wrapper directories. CreateArchive packages workspaces into downloadable zip or tar.gz.
|
||
- **Content type detection.** Extension-based MIME detection covering 40+ source code types (Go, Rust, Python, TypeScript, etc.) with `http.DetectContentType` sniffing fallback for unknown extensions.
|
||
- **`WorkspaceStore` interface.** Full CRUD for workspaces, file index operations (upsert, delete, delete-by-prefix, get, list with recursive/non-recursive modes), ownership lookup (GetByOwner, ListByOwner), and aggregate stats. Postgres and SQLite implementations.
|
||
- **Workspace API.** 15 new endpoints under `/api/v1/workspaces`: workspace CRUD (create, get, update, delete), file operations (list, read, write, delete, mkdir), archive management (upload with extraction, download), reconcile (FS→DB sync), stats. Owner-based authorization: user workspaces require self, channel workspaces require channel owner, project workspaces require project member, team workspaces require team member.
|
||
- **Unit tests.** Path cleaning (13 cases including traversal, dotfiles, whitespace), absPath traversal detection, MIME detection (14 extensions), unsafe path filtering (7 cases), common prefix detection (5 cases), write/read round-trip with mock store, delete verification, mkdir with index sync.
|
||
|
||
## [0.20.0] — 2026-03-01
|
||
|
||
### Added
|
||
- **Notifications Core (Phase 1)**: Persistent, user-targeted notification infrastructure with real-time WebSocket delivery. `notifications` table (Postgres + SQLite), `NotificationStore` with paginated queries, `Service.Notify()` / `NotifyMany()` for centralized creation and dispatch. Five API endpoints: list (paginated, filterable), unread count, mark read, mark all read, delete. Bell icon in header bar with unread count badge (capped at 9+). Notification dropdown (latest 10, grouped, click-to-navigate via `resource_type`/`resource_id`). Full notification panel registered with `PanelRegistry`. WebSocket push via `notification.new` event with toast for high-priority types (`kb.error`, `role.fallback`). Background cleanup goroutine (configurable retention, default 90 days). Initial sources: `role.fallback` (via EventBus subscription), `kb.ready`/`kb.error` (knowledge base processing), `grant.changed` (group membership).
|
||
- **@mention Parsing + Multi-model Routing (Phase 2)**: Channels support multiple AI models with @mention-based routing. `mentions.Parse()` extracts @mentions from message content, resolves against channel model roster (case-insensitive, longest-match-first, trailing punctuation tolerant). Completion handler fans out sequentially to mentioned model(s), producing one assistant response per target. Without @mention, default channel model responds (fully backward compatible). Channel model CRUD: 4 new endpoints for add/remove/update/list. Frontend: model pills in chat header, @mention autocomplete (CM6 `mentionCompletion` extension with roster-backed suggestions), model attribution labels on multi-model responses. Message `model_display` field for human-readable attribution. SSE streaming tagged with model info per response.
|
||
- **Email Transport + Notification Preferences (Phase 3)**: SMTP email delivery via `EmailTransport` supporting implicit TLS (port 465) and STARTTLS (port 587). Multipart MIME messages (HTML + plaintext) with branded templates using Go `html/template`. `notification_preferences` table with three-tier resolution: specific type → user wildcard `*` → system default (in_app=true, email=false). Three preference API endpoints (list, set with partial patch, delete). Admin SMTP configuration in settings (host, port, user, password, from address, TLS mode) with test email endpoint. User notification preferences UI in Settings → Notifications tab with per-type in-app/email checkboxes. Async email delivery (goroutine with 30s timeout, failures logged non-blocking).
|
||
- **Gin Release Mode**: Backend now automatically sets `gin.SetMode(gin.ReleaseMode)` when `ENVIRONMENT=production`, suppressing per-request access logs for health checks and reducing log noise. `GIN_MODE` env var also added to K8s backend deployment as explicit override.
|
||
|
||
### Fixed
|
||
- **Model preferences 500 on every page load**: `GET /api/v1/models/preferences` returned HTTP 500 due to `NULL` values in `user_model_settings.hidden` and `sort_order` columns failing Go `sql.Scan()` into non-pointer types. Root cause: the `Set` upsert passed `NULL` for unset patch fields, bypassing `DEFAULT false`/`DEFAULT 0` on INSERT. Fixed with `COALESCE` in both SELECT (read path) and INSERT VALUES (write path) for Postgres and SQLite. Includes data-fix SQL for existing NULL rows.
|
||
|
||
## [0.19.2] — 2026-02-28
|
||
|
||
### Added
|
||
- **Project Persona Default**: Bind a persona to a project via the detail panel. All chats in the project inherit the persona's model, parameters, and system prompt as a fallback when no explicit preset is selected. Resolution chain: explicit request → project persona → none.
|
||
- **Project Archive Toggle**: Archive/unarchive projects from the detail panel. Archived projects hidden from sidebar by default with "Show archived (N)" toggle. Dimmed visual treatment when shown.
|
||
- **Channel Reorder**: Right-click a chat within a project for "Move up" / "Move down" options. Server-persisted positions via `project_channels.position`, loaded on startup, maintained across moves.
|
||
|
||
## [0.19.1] — 2026-02-28
|
||
|
||
### Added
|
||
- **Active Project**: Pin a project as active; new chats auto-assign to it. Persists across reloads via localStorage. Visual indicator (📌 + accent border) in sidebar.
|
||
- **Project System Prompt**: Per-project instructions stored in `projects.settings` JSONB. Injected between persona/channel prompt and KB hint during completion. Merge semantics on update (preserves other settings keys).
|
||
- **Project Detail Panel**: Side panel (via PanelRegistry) for managing project system prompt, KB bindings, and notes. Accessible from project ⋯ menu → "Project settings".
|
||
- Enriched `ListKBs` and `ListNotes` responses with JOIN-sourced `name`/`title` fields for display in the project panel.
|
||
|
||
### Changed
|
||
- `ProjectPatch` model now accepts `settings` field for partial settings merge.
|
||
- Project context menu expanded: "Pin as active", "Project settings", plus existing rename/color/delete.
|
||
|
||
## [0.19.0] — 2026-02-28
|
||
|
||
### Added
|
||
- **Projects / Workspaces.** Organizational containers that group related
|
||
conversations, knowledge bases, and notes into a single workspace.
|
||
Projects provide a scope-aware organizational layer above individual
|
||
channels, with support for personal, team, and global visibility.
|
||
- **Project data model.** Four new tables: `projects` (with scope, owner,
|
||
team, color, icon, settings JSONB), `project_channels` (ordered
|
||
membership with UNIQUE constraint), `project_knowledge_bases` (with
|
||
auto_search flag), and `project_notes`. Channels gain a denormalized
|
||
`project_id` FK for efficient filtering.
|
||
- **Project API.** 17 new endpoints under `/api/v1/projects`: full CRUD,
|
||
channel add/remove/list/reorder, KB add/remove/list, note
|
||
add/remove/list. Access checks enforce owner-or-team-member visibility
|
||
with owner-only delete.
|
||
- **Project KB resolution.** Knowledge bases bound to a project are
|
||
automatically available to all channels within that project. Resolution
|
||
chain extended: Persona KBs → **Project KBs** → Channel KBs →
|
||
Personal KBs. Both `BuildKBHint` (system prompt injection) and
|
||
`kbsearch` (tool-time retrieval) updated.
|
||
- **Note auto-association.** Notes created from a channel that belongs to
|
||
a project are automatically added to that project's note collection.
|
||
- **Sidebar project groups.** Projects appear as collapsible groups in the
|
||
sidebar above the existing time-based "Recent" section. Each group shows
|
||
a color dot, chat count, and an options menu (⋯) for rename, color
|
||
picker, and delete.
|
||
- **Drag-and-drop.** Drag chat items between project groups or back to
|
||
Recent. Visual feedback with outline and background highlight on valid
|
||
drop targets.
|
||
- **Right-click context menu.** Right-click any chat to move it to a
|
||
project, remove it from its current project, or create a new project
|
||
and assign in one step.
|
||
- **New Project button.** Added to the New Chat split-button dropdown for
|
||
quick project creation.
|
||
- **Channel project_id filter.** `GET /api/v1/channels` accepts
|
||
`?project_id=<uuid>` to filter by project, or `?project_id=none` for
|
||
unassigned channels. Response includes `project_id` field.
|
||
|
||
### Changed
|
||
- `renderChatList()` rewritten to support project grouping while
|
||
preserving the original time-based layout when no projects exist.
|
||
Chat items now include `draggable`, `oncontextmenu`, and drag event
|
||
handlers.
|
||
- Channel response struct, SELECT queries, and scan calls updated across
|
||
`ListChannels`, `GetChannel`, and `CreateChannel` (both Postgres and
|
||
SQLite paths) to include `project_id`.
|
||
- `resource_grants` CHECK constraint extended to include `'project'` as a
|
||
valid resource type.
|
||
- `db-validate.sh` updated: former "Dropped tables" assertions for
|
||
`projects` and `project_channels` replaced with positive checks for all
|
||
four project tables plus `channels.project_id` column check.
|
||
- Test helper truncation list updated with project junction tables.
|
||
|
||
### Fixed
|
||
- **JSON corruption defense** (hotfix carry-forward from 0.18.2):
|
||
`SafeJSON` wrapper with `scanJSON` and `scanTags` hardened helpers that
|
||
replace bare `json.RawMessage` / `pq.Array` scanning. Prevents
|
||
`encoding/json: invalid character` panics from NULL or empty columns.
|
||
- **Service worker** (hotfix carry-forward): `chrome-extension://` URL
|
||
filtering to avoid opaque response cache errors.
|
||
|
||
## [0.18.1] — 2026-02-28
|
||
|
||
### Added
|
||
- **Side panel architecture.** Complete rewrite of the side panel system
|
||
from shared-tab layout to independent single-slot panels. Any action
|
||
(preview, notes, diagram pop-out) fills the slot, replacing whatever
|
||
was there — no tabs, no association between panel types.
|
||
- **Panel registry** (`PanelRegistry`): named panels with independent
|
||
open/close state, scroll/state preservation across switches, keyboard
|
||
shortcuts (Ctrl+\ cycle, Ctrl+Shift+\ toggle dual-view).
|
||
- **Dual-view mode**: two panels side-by-side via CSS grid with a
|
||
drag-adjustable split ratio (0.2–0.8 range, 6px divider handle).
|
||
Toggle button in header actions area alongside fullscreen and close.
|
||
- **Live HTML preview**: streaming responses update the preview iframe
|
||
in real-time (500ms debounce). Extracts the last fenced HTML block
|
||
from partial content and renders it as the response streams in.
|
||
- **Extension pop-out**: `⧉` button on rendered extension blocks
|
||
(mermaid, KaTeX, etc.) clones the content into the preview iframe
|
||
and opens the side panel.
|
||
- **Extension UI primitives** (`ctx.ui`): seven methods exposed to
|
||
browser extensions through the scoped extension context:
|
||
- `toast(msg, type)` — toast notifications via `UI.toast()`
|
||
- `openPreview(html)` — load HTML into side panel preview iframe
|
||
- `isDark()` — theme detection without DOM sniffing
|
||
- `isMobile()` — viewport width check (≤768px)
|
||
- `isPanelOpen()` — side panel container visibility
|
||
- `confirm(msg, opts)` — modal confirm dialog (Promise\<boolean>)
|
||
- `createMenu(anchor, opts)` — popup menu (unchanged from stub)
|
||
- **Mermaid context-aware expand**: single `⛶` button replaces the
|
||
previous two-button (pop-out + fullscreen) approach. When the side
|
||
panel is open, expand pops the diagram into it; when closed, expand
|
||
goes fullscreen.
|
||
- **Mermaid fullscreen close button**: 40px circular close button
|
||
overlaid top-right in fullscreen mode, 48px on mobile. Fixes the
|
||
previous Escape-key-only exit which was unusable on touch devices.
|
||
- **Mobile side panel**: swipe navigation between panels (80px
|
||
threshold, 1.5× horizontal-to-vertical ratio), tap-to-close overlay,
|
||
responsive auto-collapse of dual mode on narrow viewports, enlarged
|
||
touch targets for all panel controls.
|
||
- **Side panel header label**: simple text label showing the active
|
||
panel name, replacing the previous tab bar UI.
|
||
|
||
### Changed
|
||
- Side panel model changed from tabbed (Preview + Notes tabs visible
|
||
simultaneously) to single-slot (one panel fills the space, actions
|
||
replace it). No user-selectable tabs — content is entirely
|
||
action-driven.
|
||
- Dual-view toggle button moved from tab bar (dynamically injected)
|
||
to static header actions area next to fullscreen and close.
|
||
- Mermaid extension refactored to use `ctx.ui` primitives exclusively.
|
||
Zero direct references to `UI.*`, `PanelRegistry.*`, or DOM class
|
||
sniffing for theme detection. Extension remains fully self-contained
|
||
in `extensions/builtin/mermaid-renderer/`.
|
||
- Mermaid source copy uses `ctx.ui.toast()` instead of inline button
|
||
text swap. Theme detection uses `ctx.ui.isDark()` instead of manual
|
||
`document.body.classList.contains('dark-theme')` check.
|
||
- Side panel outer resize minimum bumped to 480px in dual mode (vs
|
||
280px single).
|
||
|
||
### Removed
|
||
- Tab bar UI (`.side-panel-tabs`, `.side-panel-tab`, tab rendering
|
||
logic). Panels no longer have user-selectable tabs.
|
||
- Separate pop-out and fullscreen buttons in mermaid toolbar (collapsed
|
||
into single context-aware expand button).
|
||
|
||
## [0.18.0] — 2026-02-28
|
||
|
||
### Added
|
||
- **Memory system.** Long-term memory across conversations with scope-aware
|
||
isolation. Three memory scopes: `user` (personal facts/preferences),
|
||
`persona` (shared across all users of a Persona), and `persona_user`
|
||
(per-user within a Persona context, e.g. tutoring progress per student).
|
||
Memories persist across channels and are injected into the system prompt
|
||
at completion time with scope priority (persona_user > persona > user).
|
||
- **Memory tools.** Two new LLM-callable tools:
|
||
- `memory_save` — LLM explicitly stores a fact with key/value/confidence.
|
||
Scope-aware: saves to the active scope for the current Persona context.
|
||
- `memory_recall` — LLM queries stored facts relevant to current context.
|
||
Merges results from applicable scopes with semantic search via embeddings
|
||
and keyword fallback.
|
||
- **Automatic memory extraction.** Background scanner finds conversations
|
||
with sufficient new activity, sends them to the utility model role for
|
||
fact extraction, and stores results as `pending_review` memories.
|
||
Configurable extraction prompt per Persona (e.g. "extract FAQ-worthy
|
||
Q&A pairs"). Scanner runs on a configurable interval with concurrency
|
||
control. Global kill switch via admin settings.
|
||
- **Memory review pipeline.** Extracted memories start in `pending_review`
|
||
status. Admin panel shows pending review queue with bulk approve. Users
|
||
can approve/reject/edit their own pending memories from the Settings →
|
||
Memory tab.
|
||
- **User memory management.** Settings → Memory tab shows active/pending
|
||
memory counts, filterable/searchable memory list with inline edit and
|
||
delete. Status filter (active, pending_review, archived). Approve All
|
||
button for batch operations on pending memories.
|
||
- **Admin memory controls.** Admin panel → Memory section shows system-wide
|
||
pending review queue. Memory extraction toggle in admin Settings panel
|
||
(`memory_extraction_enabled`). Bulk approve endpoint for batch review.
|
||
- **Hybrid semantic recall.** Memory recall supports both keyword search
|
||
and vector similarity (cosine distance). Postgres uses `<=>` operator
|
||
with pgvector; SQLite computes cosine similarity in Go with app-level
|
||
vector loading. Results are merged and deduplicated.
|
||
- **Memory injection at completion time.** `BuildMemoryHint()` loads
|
||
relevant memories and formats them as a system prompt section, injected
|
||
after the knowledge base hint. Context-budget aware with configurable
|
||
character limit. Supports embedding-based relevance filtering when the
|
||
user's latest message is available.
|
||
- **Persona memory configuration.** Personas gain `memory_enabled` (bool)
|
||
and `memory_extraction_prompt` (text) fields. When memory is enabled on
|
||
a Persona, conversations with that Persona contribute to persona-scoped
|
||
memory extraction. Custom extraction prompts allow specialization
|
||
(e.g. helpdesk FAQ extraction vs. tutoring progress tracking).
|
||
- **Database migrations.** Four new migration files (Postgres + SQLite):
|
||
- `004_v0180_memories.sql` / `sqlite/003_v0180_memories.sql` — `memories`
|
||
table with composite unique index, full-text search index (GIN/keyword),
|
||
`memory_extraction_log` tracking table.
|
||
- `005_v0180_memory_phase2.sql` / `sqlite/004_v0180_memory_phase2.sql` —
|
||
Persona memory columns, extraction log unique constraint.
|
||
- **API endpoints.** Eight new authenticated endpoints:
|
||
- `GET /memories` — list user's memories (filterable by status/query)
|
||
- `GET /memories/count` — active + pending counts
|
||
- `PUT /memories/:id` — edit a memory's key/value/confidence
|
||
- `DELETE /memories/:id` — delete a memory
|
||
- `POST /memories/:id/approve` — approve a pending memory
|
||
- `POST /memories/:id/reject` — reject (archive) a pending memory
|
||
- `GET /admin/memories/pending` — admin pending review queue
|
||
- `POST /admin/memories/bulk-approve` — admin bulk approve
|
||
|
||
### Changed
|
||
- `CompletionHandler` now accepts an `*knowledge.Embedder` parameter for
|
||
memory injection with semantic relevance filtering.
|
||
- Persona create/update forms include memory configuration fields
|
||
(enabled toggle, extraction prompt textarea).
|
||
- Admin settings save handler includes `memory_extraction` and
|
||
`memory_extraction_enabled` keys.
|
||
- `store.Stores` struct includes `Memories MemoryStore` field.
|
||
- Admin panel sections include Memory with pending review loader.
|
||
|
||
### Technical Notes
|
||
- Memory embeddings use `vector(3072)` matching the existing KB/notes
|
||
schema. HNSW index is not used (pgvector limits HNSW to 2000 dims);
|
||
filtered sequential scans are performant for per-user memory tables.
|
||
IVFFlat or dimension reduction available as future optimizations.
|
||
- SQLite hybrid recall loads embeddings into Go and computes cosine
|
||
similarity at the application level, reusing the existing
|
||
`cosineSimilarity()` function from the knowledge base store.
|
||
- Memory tools use late registration (like KB search and note tools)
|
||
because they require stores and embedder dependencies initialized
|
||
in main.go.
|
||
|
||
## [0.17.3] — 2026-02-28
|
||
|
||
### Added
|
||
- **Wikilink bi-directional linking.** Notes support `[[Title]]` and
|
||
`[[Title|display text]]` syntax. Links are extracted on save via regex,
|
||
resolved to target note IDs by case-insensitive title match, and stored
|
||
in a `note_links` junction table. Dangling links (references to notes
|
||
that don't exist yet) are preserved and automatically resolved when a
|
||
matching note is created later.
|
||
- **Transclusion embeds.** `![[Title]]` syntax renders embedded note content
|
||
inline in read mode. Content is fetched asynchronously with a recursion
|
||
guard (max depth 1 — nested transclusions render as plain text references).
|
||
Transclusion links are visually distinct in both the editor (border-left,
|
||
italic) and the graph (dashed edges).
|
||
- **Backlinks panel.** Read mode shows a collapsible "Linked mentions" panel
|
||
below the note content listing all notes that link to the current note.
|
||
Each backlink is clickable to navigate directly. Count badge updates on
|
||
every note open.
|
||
- **Knowledge graph visualization.** Canvas-based force-directed graph with
|
||
no external dependencies (~480 lines). Physics: O(n²) Coulomb repulsion,
|
||
Hooke spring attraction on edges, center gravity. Interaction: pan, zoom
|
||
(0.15–4.0x toward cursor), drag nodes, hover highlights node + neighbors.
|
||
Click opens note editor. Nodes sized by √(link_count), colored by folder
|
||
path (10-color palette). Ghost nodes for unresolved `[[links]]` with
|
||
toggle button. Energy-based pause (stops rAF when total kinetic energy
|
||
falls below threshold). ResizeObserver for responsive canvas sizing.
|
||
- **CM6 note editor.** New `CM.noteEditor()` factory function with live
|
||
markdown preview (heading sizes for h1–h3, blockquote styling, fenced code
|
||
block decorations), `[[wikilink]]` autocomplete triggered by `[[` with
|
||
async title search, and clickable wikilink chip rendering. Falls back to
|
||
plain `<textarea>` if CM6 bundle is unavailable.
|
||
- **Wikilink autocomplete.** Typing `[[` in the note editor triggers an
|
||
async completion popup backed by `GET /notes/search-titles?q=`. Results
|
||
are fetched via `ILIKE` title match, limited to 8 suggestions. Selecting
|
||
a result inserts the full `[[Title]]` syntax.
|
||
- **Daily notes.** "Today" button in the notes toolbar creates or opens a
|
||
daily note titled `Daily — YYYY-MM-DD` in the `/daily/` folder with a
|
||
starter template (Tasks + Notes sections). Idempotent — repeated clicks
|
||
navigate to the existing daily note.
|
||
- **Save-to-note from chat.** "Note" button in message action bar captures
|
||
the full message content (or the current text selection within that message)
|
||
into a new note with pre-filled title extracted from the first line.
|
||
Provenance is tracked via `source_message_id` column on the notes table,
|
||
enabling future jump-to-source navigation.
|
||
- **API endpoints.** Three new authenticated endpoints:
|
||
- `GET /notes/search-titles?q=&limit=` — lightweight title search for
|
||
autocomplete (returns `[{id, title}]`).
|
||
- `GET /notes/:id/backlinks` — notes linking to the specified note, with
|
||
display text and metadata.
|
||
- `GET /notes/graph` — full graph topology: nodes (with inbound/outbound
|
||
link counts), resolved edges, and unresolved (dangling) link titles.
|
||
- **`note_links` table.** New junction table with `source_note_id`,
|
||
`target_note_id` (nullable for dangling links), `target_title`,
|
||
`display_text`, `is_transclusion`, and `created_at`. Primary key on
|
||
`(source_note_id, target_title)`. Index on `target_note_id` for backlink
|
||
queries. Migrations for both Postgres and SQLite.
|
||
- **`source_message_id` column.** New nullable UUID column on `notes` table
|
||
for chat-to-note provenance tracking.
|
||
- **Wikilink extraction package.** Standalone `notelinks/` package with
|
||
regex-based parser handling `[[Title]]`, `[[Title|Display]]`,
|
||
`![[Title]]`, and `![[Title|Display]]`. Deduplicates by lowercase title,
|
||
preserves first occurrence. 10 unit tests covering edge cases.
|
||
|
||
### Changed
|
||
- Notes editor replaces `<textarea>` with CM6 `noteEditor` instance. The
|
||
container `#noteEditorContentContainer` is lazily initialized on first
|
||
edit; destroyed on panel close to avoid stale state.
|
||
- Note create and update handlers now extract wikilinks from content and
|
||
call `ReplaceLinks()` (transactional DELETE + batch INSERT). Create also
|
||
calls `ResolveByTitle()` to fix dangling links from other notes.
|
||
- `showNotesList()` destroys the CM6 editor instance and hides graph view.
|
||
- `saveNote()` and `deleteNote()` call `invalidateNoteGraph()` to clear
|
||
the cached graph data.
|
||
- Read mode renders `[[wikilinks]]` as styled clickable chips via post-
|
||
processing of the formatted HTML. Clicking navigates to the linked note
|
||
or offers to create it if not found.
|
||
- `ARCHITECTURE.md` updated with `notelinks/` package, `note-graph.js`,
|
||
`CM.noteEditor()` factory, and expanded notes description covering the
|
||
linking model.
|
||
- `ROADMAP.md` updated with full v0.17.3 section including all checklist
|
||
items.
|
||
- CM6 bundle entrypoint (`index.mjs`) exports `noteEditor` alongside
|
||
existing `chatInput` and `codeEditor`.
|
||
- `theme.mjs` adds `noteEditorTheme` with full-height layout (min 200px,
|
||
max 60vh), heading size decorations, and blockquote styling.
|
||
|
||
## [0.17.2] — 2026-02-28
|
||
|
||
### Added
|
||
- **CodeMirror 6 integration.** Rich editor infrastructure compiled at Docker
|
||
build time via esbuild (IIFE bundle, ~295KB min / ~90KB gzip). Two factory
|
||
functions exposed on `window.CM`:
|
||
- `CM.chatInput()` — Markdown-mode editor for the chat input with
|
||
auto-growing height, Enter=send / Shift+Enter=newline, spell check, and
|
||
WYSIWYG fenced code block decorations (visual container with monospace
|
||
font and accent border, matching claude.ai UX).
|
||
- `CM.codeEditor()` — Full-featured code editor for admin extension panel
|
||
with line numbers, bracket matching, search/replace, fold gutter, and 10
|
||
bundled language modes (Markdown, JavaScript, JSON, SQL, HTML, CSS, YAML,
|
||
Go, Python, Rust).
|
||
- **Graceful degradation.** All CM6 integration points check `window.CM`
|
||
availability. If the bundle fails to load, the app falls back to native
|
||
`<textarea>` with zero breakage.
|
||
- **Dark/Light/System theme toggle.** New appearance setting with three
|
||
modes. Light theme overrides all CSS variables via `[data-theme="light"]`
|
||
selector. System mode tracks `prefers-color-scheme` media query in real
|
||
time. Theme changes emit `theme.changed` on the EventBus; CM6 editors
|
||
toggle `oneDark` syntax theme via compartment reconfiguration.
|
||
- **Vim/Emacs keybinding preference.** Editor keybinding mode (Standard /
|
||
Vim / Emacs) configurable in appearance settings. Applies to code editors
|
||
and extension editors only — chat input always uses standard keybindings.
|
||
Live-switchable on already-open editors via `keymap.changed` event.
|
||
Bundled statically (~40KB for both modes).
|
||
- **Inline code shortcut.** Ctrl/Cmd+E wraps selection in backticks or
|
||
inserts an empty inline code pair with cursor between them.
|
||
- **Code block shortcut.** Typing ` ``` ` at the start of a line expands
|
||
to a fenced code block with cursor positioned inside. The decoration plugin
|
||
renders code blocks with a styled visual container in the chat input.
|
||
- **CI path-based change detection.** New `detect-changes` job classifies
|
||
changed files into frontend/backend/infra/docs buckets. Test jobs skip
|
||
when irrelevant (FE-only changes skip Go tests, docs-only changes skip
|
||
all tests and deploy). Tags always run the full pipeline.
|
||
|
||
### Changed
|
||
- Docker build pipeline: both `Dockerfile.frontend` and unified `Dockerfile`
|
||
now include a `cm6-build` stage (Node 20 Alpine → esbuild → IIFE bundle).
|
||
- `ChatInput` abstraction in `chat.js` replaces direct textarea access
|
||
across 7 callsites (`chat.js`, `tokens.js`, `attachments.js`).
|
||
- Extension editor in `admin-handlers.js` uses `CM.codeEditor()` with JSON
|
||
and JavaScript modes, replacing bare `<textarea>` with manual Tab handler.
|
||
- Service worker excludes `/vendor/codemirror/` from cache (version-busted).
|
||
- Debug state snapshot includes CM6 version and language list.
|
||
- `ARCHITECTURE.md` updated to v0.17 reflecting CM6 integration, SQLite
|
||
dual-driver, modular frontend file structure, and theme system.
|
||
- CI pipeline header updated to v0.17.2 with path gating documentation.
|
||
- `build-editor.sh` falls back to `npm install` if `package-lock.json`
|
||
is missing (belt-and-suspenders for local dev).
|
||
|
||
### Fixed
|
||
- **App initialization crash.** `Events.publish` (nonexistent) → `Events.emit`
|
||
(correct API). The unhandled exception during `initAppearance()` killed
|
||
`initListeners()`, leaving the entire app half-initialized — settings modal
|
||
unclosable, keyboard shortcuts unwired, paste handlers missing.
|
||
- **Cursor invisible in dark and light mode.** CM6 cursor used `--accent`
|
||
color (low contrast on both themes). Switched to `--text` for consistent
|
||
visibility. Added explicit `borderLeftWidth: 2px`.
|
||
- **Placeholder text offset.** Double padding between `.cm-editor` wrapper
|
||
(12px) and `.cm-content` (8px) pushed placeholder 20px below expected
|
||
position. Zeroed `.cm-content` padding — wrapper is the single source of
|
||
truth.
|
||
|
||
## [0.17.1] — 2026-02-27
|
||
|
||
### Added
|
||
- **SQLite backend.** Full dual-driver database layer — set `DB_DRIVER=sqlite`
|
||
to run with an embedded SQLite database. Pure Go (no CGO), zero external
|
||
dependencies. 19 store files covering all domain stores: channels, messages,
|
||
users, teams, personas, knowledge bases, notes, usage, audit, extensions,
|
||
and more. Feature parity with Postgres including knowledge base vector
|
||
search via app-level cosine similarity computed in Go.
|
||
- **Dialect-aware test infrastructure.** `database.SetupTestDB()` detects
|
||
`DB_DRIVER` and provisions either a Postgres test database or a SQLite
|
||
temp file. Exported `database.PH(n)` returns `$N` or `?` per dialect.
|
||
`database.TruncateAll()` uses `TRUNCATE CASCADE` on Postgres and
|
||
`DELETE FROM` with `PRAGMA foreign_keys` toggling on SQLite.
|
||
`dialectSQL()` helper in handler tests converts `$N` placeholders and
|
||
strips `::jsonb` casts at runtime.
|
||
- **SQLite CI pipeline.** New `test-sqlite` job runs the full handler
|
||
integration test suite and store tests against an embedded SQLite
|
||
database. Parallel with the existing Postgres test job. Build gate
|
||
verifies `CGO_ENABLED=0` compilation.
|
||
- **Generic provider test config.** Live provider integration tests now
|
||
read `PROVIDER`, `PROVIDER_KEY`, and `PROVIDER_URL` environment
|
||
variables instead of hardcoded `VENICE_API_KEY`. Legacy fallback
|
||
preserved. Model selection prefers non-reasoning models to avoid
|
||
thinking budget requirements with low `max_tokens`.
|
||
|
||
### Changed
|
||
- `kb_chunks` table in SQLite schema includes `embedding TEXT` column
|
||
for JSON-encoded float64 vectors (previously omitted as feature-gated).
|
||
- `SimilaritySearch` on SQLite loads candidate chunks, decodes JSON
|
||
embeddings, and computes cosine distance in Go — replacing the
|
||
previous "not available" error.
|
||
- `InsertChunks` on SQLite now stores embedding vectors as JSON text.
|
||
- Live test names genericized: `TestLive_Venice*` → `TestLive_*`.
|
||
- CI `build-and-deploy` depends on `[test, test-frontend, test-sqlite]`.
|
||
|
||
### Fixed
|
||
- **SQLite `RETURNING` + `time.Time` scan failure.** The modernc/sqlite
|
||
driver cannot scan `datetime('now')` TEXT columns into `time.Time` via
|
||
`RETURNING`. All 13 SQLite store `Create` methods rewritten to set
|
||
timestamps in Go (`time.Now().UTC()`) and use `ExecContext` instead of
|
||
`QueryRowContext(...).Scan()`. Format: `2006-01-02 15:04:05` (`timeFmt`
|
||
constant in `helpers.go`).
|
||
- **SQLite missing `id` in INSERTs.** Unlike Postgres (`DEFAULT
|
||
gen_random_uuid()`), SQLite `TEXT PRIMARY KEY` columns have no
|
||
auto-generation. Added `store.NewID()` / `uuid.New()` to: `usage_log`,
|
||
`model_pricing` (both upsert paths), `team_members` (AddMember),
|
||
`group_members` (AddMember), `refresh_tokens` (CreateRefreshToken).
|
||
- **Test seed helpers SQLite-aware.** `SeedTestUser`, `SeedTestChannel`,
|
||
`SeedTestTeam`, `SeedTestTeamMember`, `SeedTestGroup`, `SeedGroupMember`
|
||
now branch on `IsSQLite()` to provide application-generated UUIDs
|
||
instead of relying on `RETURNING id`.
|
||
- **Postgres-isms in SQLite stores.** `extension.go` Update used `now()`
|
||
→ `datetime('now')`; `ListForUser` COALESCE used `true` → `1`.
|
||
- SQLite store bool/int type mismatches: `persona.go` auto-fetch flag,
|
||
`usage.go` exclude-BYOK filter, `user_settings.go` visibility map
|
||
values — all corrected from `0`/`1` to `false`/`true`.
|
||
|
||
## [0.17.0] — 2026-02-27
|
||
|
||
### Added
|
||
- **Persona-KB binding.** Personas can now have knowledge bases directly
|
||
bound to them. When a user selects a persona with bound KBs, the
|
||
`kb_search` tool is automatically scoped to those KBs and the persona's
|
||
system prompt includes a KB listing hint. Admin and team admin preset
|
||
forms include a KB picker with per-KB auto-search toggles.
|
||
Migration adds `persona_knowledge_bases` join table.
|
||
- **Enterprise KB mode.** New `discoverable` flag on knowledge bases
|
||
controls whether users can see and attach KBs directly. When
|
||
`kb_direct_access` platform policy is set to `true`, users cannot
|
||
attach KBs to channels directly — they access KBs exclusively through
|
||
persona bindings curated by admins. New `ListDiscoverableKBs` and
|
||
`SetDiscoverable` endpoints.
|
||
- **Role fallback alerts (issue #69).** When a role's primary provider
|
||
fails and the fallback activates, the resolver emits a `role.fallback`
|
||
event on the EventBus with audit log entry. Admin users see a persistent
|
||
dismissable banner. 5-minute per-role cooldown prevents flooding.
|
||
- **Chat rename.** Double-click any chat title in the sidebar to edit
|
||
inline. Enter saves, Escape cancels.
|
||
- **Utility model auto-naming.** After the first assistant response,
|
||
a background request to the utility role generates a concise title.
|
||
Falls back to truncation when no utility model is configured.
|
||
New endpoint: `POST /channels/:id/generate-title`.
|
||
- **Chat token count.** Conversation token estimate shown in the model
|
||
bar, color-coded against context budget.
|
||
- **State restore on refresh.** Active chat ID persisted to
|
||
`sessionStorage`, auto-restored on page reload.
|
||
|
||
### Fixed
|
||
- **ResolvePreset group access bypass.** `ResolvePreset()` used raw SQL
|
||
that skipped `resource_grants` checks from v0.16.0. Now uses
|
||
`PersonaStore.UserCanAccess()`.
|
||
- **KB create scope authorization.** Now enforces admin role for global
|
||
KBs and team admin role for team KBs.
|
||
- **Completion handler persona ID scoping.** `personaID` variable scoped
|
||
inside an `if` block but referenced downstream. Fixed by threading
|
||
through function signatures.
|
||
- **Nil slice JSON marshaling.** `ListDiscoverableKBs` returns `[]`
|
||
instead of `null` when no KBs match.
|
||
|
||
### Changed
|
||
- `UpdateDocumentStorageKey` moved from raw `ExecContext` to store method.
|
||
- EventBus route table: `role.fallback` → `DirToClient`.
|
||
- Resolver gains `.WithBus(bus)` builder (nil-safe, backwards compatible).
|
||
- Service worker: `/extensions/` excluded from fetch handler.
|
||
- Mermaid renderer v2.0 (pan/zoom, export) promoted to builtin.
|
||
- Removed DIAG diagnostics from `TestGroupBasedPersonaAccess`.
|
||
- Paste-to-file threshold synced from backend `PublicSettings`
|
||
(`storage.paste_to_file_chars`, admin-configurable, default 2000).
|
||
Resolves hardcoded constant in `attachments.js`.
|
||
- Embedding dropdown already had tolerant type filter, manual model ID
|
||
fallback, and auto-switch on empty — confirmed complete, checkbox updated.
|
||
|
||
## [0.16.0] — 2026-02-27
|
||
|
||
### Added
|
||
- **User groups.** Global and team-scoped groups decoupled from team
|
||
membership. `groups` and `group_members` tables. Admin and team admin
|
||
CRUD for group management. Groups serve as ACL targets for resources.
|
||
- **Resource grants.** Three-way grant model (`team_only`, `global`,
|
||
`groups`) for Personas and Knowledge Bases. `resource_grants` table
|
||
with `grant_scope` and `granted_groups UUID[]` columns. Grant picker
|
||
UI on Persona and KB forms with group multi-select.
|
||
- **Schema consolidation.** 9 incremental migrations collapsed into
|
||
single `001_v016_schema.sql`. Fresh installs use the consolidated
|
||
file; upgrade path preserved via migration version tracking.
|
||
|
||
### Changed
|
||
- Persona and KB list queries now filter through `resource_grants` for
|
||
non-admin users. Team-only remains the default scope.
|
||
- Admin panel shows group membership counts and grant summaries.
|
||
|
||
## [0.15.1] — 2026-02-26
|
||
|
||
### Added
|
||
- **`attachment_recall` tool.** Two operations: `list` returns filenames
|
||
and metadata for the current channel's attachments; `read` extracts
|
||
and returns content by attachment ID. Channel-scoped access control.
|
||
- **`conversation_search` tool.** Full-text search across the current
|
||
channel's message history using PostgreSQL `plainto_tsquery`. Returns
|
||
matching messages with timestamps and role context.
|
||
- **Token estimator attachment awareness.** `Tokens.estimateAttachments()`
|
||
accounts for staged file sizes in context budget calculations. Warning
|
||
thresholds include attachment estimates.
|
||
|
||
## [0.15.0] — 2026-02-26
|
||
|
||
### Added
|
||
- **Background compaction scanner.** Periodic scan identifies channels
|
||
exceeding configurable context thresholds. Automatic summarization via
|
||
utility role compresses old messages into summary nodes. Per-channel
|
||
opt-in/out via `auto_compact` channel setting.
|
||
- **Compaction service.** `compaction.Service` orchestrates summary
|
||
generation: estimates token usage, selects messages for compression,
|
||
calls utility role, inserts summary as tree boundary node with metadata.
|
||
- **Context budget guard rail.** 80% ceiling prevents compaction from
|
||
triggering mid-generation. Cooldown timer prevents repeated compaction
|
||
of the same channel.
|
||
- **Summarize & Continue button.** User-triggered compaction from the
|
||
context warning bar. Reuses compaction service with immediate execution.
|
||
|
||
### Changed
|
||
- Scanner configurable via `global_settings`: threshold percentage,
|
||
cooldown duration, enabled/disabled toggle. Channel-level overrides.
|
||
|
||
## [0.14.0] — 2026-02-26
|
||
|
||
### Added
|
||
- **Knowledge bases.** RAG pipeline: upload documents → chunk (recursive
|
||
text splitter) → embed via pgvector → `kb_search` tool for semantic
|
||
retrieval. Team and personal KB scopes. Channel KB toggle enables
|
||
per-conversation knowledge access.
|
||
- **Document ingestion.** `knowledge.Ingest()` pipeline: file upload →
|
||
text extraction (reuses v0.12.0 pipeline) → chunking with configurable
|
||
overlap → embedding via the embedding role → storage as `kb_chunks`
|
||
with vector index.
|
||
- **KB admin panel.** Knowledge Bases section under AI category. Create,
|
||
delete, upload documents, view chunk counts and storage usage. Team
|
||
admin scoped to team KBs.
|
||
- **Notes semantic search.** Note search upgraded from exact text match
|
||
to pgvector cosine similarity when embeddings are available.
|
||
- **`kb_search` tool.** Registered when embedding role is configured.
|
||
Accepts query text, returns top-K chunks with source document
|
||
attribution. Channel KB bindings control which KBs are searched.
|
||
|
||
### Changed
|
||
- pgvector extension enabled in schema (`CREATE EXTENSION IF NOT EXISTS
|
||
vector`). `kb_chunks` table includes `embedding vector(1536)` column
|
||
with IVFFlat index.
|
||
|
||
## [0.13.1] — 2026-02-26
|
||
|
||
### Added
|
||
- **`web_search` tool.** Search provider abstraction with two backends:
|
||
DuckDuckGo (HTML scraping, zero config) and SearXNG (self-hosted,
|
||
JSON API). Returns title, URL, and snippet for each result. Configured
|
||
via `SEARCH_PROVIDER` and `SEARXNG_URL` env vars.
|
||
- **`url_fetch` tool.** Fetches and extracts text content from URLs.
|
||
Respects robots.txt. Content-type detection with HTML-to-text
|
||
conversion. Configurable timeout and size limits.
|
||
- **Tool categories.** Tools now have a `category` field (builtin,
|
||
search, knowledge, browser). Tools toggle UI in chat bar groups
|
||
by category with per-tool enable/disable.
|
||
- **Tools toggle UI.** Popup menu on chat input toolbar showing all
|
||
available tools. Per-tool checkbox state sent as `disabled_tools[]`
|
||
in completion requests. Browser extension tools included.
|
||
|
||
## [0.13.0] — 2026-02-25
|
||
|
||
### Changed
|
||
- **Admin panel refactor.** Replaced 12-tab modal with fullscreen admin
|
||
panel. Four categories (People, AI, System, Monitoring) with section
|
||
sidebar navigation. URL-based routing (`#admin/people/users`).
|
||
Responsive layout, classification banner-aware positioning.
|
||
- **CSS design token cleanup.** Consolidated duplicate color/spacing
|
||
variables. Admin panel uses shared token system with main UI.
|
||
|
||
## [0.12.0] — 2026-02-25
|
||
|
||
### Added
|
||
- **File handling and vision support.** Upload images, PDFs, and documents
|
||
into chat via 📎 button, drag-and-drop, or paste. Multimodal message
|
||
assembly injects base64 images for vision-capable models and extracted
|
||
text for documents. Staged attachment strip shows upload progress and
|
||
extraction status. Auth-aware blob rendering with Bearer tokens. Image
|
||
lightbox viewer. Vision capability hints on image attachments.
|
||
- **Storage backend abstraction.** `ObjectStore` interface with two
|
||
implementations: PVC (local filesystem) for single-node/dev and S3
|
||
(minio-go) for multi-node production. S3 backend works with any
|
||
S3-compatible API: MinIO, Ceph RGW, AWS S3. Auto-detection when
|
||
`STORAGE_BACKEND` is not set (tries PVC, disables if not writable).
|
||
Both backends share identical semantics — all handlers, attachment
|
||
CRUD, multimodal assembly, and orphan cleanup work regardless of
|
||
backend.
|
||
- **S3 configuration.** `S3_ENDPOINT`, `S3_BUCKET`, `S3_ACCESS_KEY`,
|
||
`S3_SECRET_KEY`, `S3_REGION`, `S3_PREFIX`, `S3_FORCE_PATH_STYLE` env
|
||
vars. Endpoint auto-detects SSL from scheme. Path-style URLs default
|
||
on (required for MinIO/Ceph). Optional key prefix for shared buckets.
|
||
CI pipeline syncs S3 secrets to K8s when `STORAGE_BACKEND=s3`.
|
||
- **Text extraction pipeline.** PDF, DOCX, XLSX, PPTX, ODT, RTF text
|
||
extraction via filesystem-based queue. Inline and sidecar modes.
|
||
Crash recovery for items stuck in processing state.
|
||
- **Admin storage panel.** Backend health, file count, total size,
|
||
orphan detection and cleanup. Shows PVC path or S3 endpoint/bucket
|
||
depending on active backend. Extraction queue status.
|
||
- **Vault CLI commands.** `switchboard vault rekey` re-encrypts all
|
||
provider API keys when rotating the encryption key.
|
||
`switchboard vault status` shows encryption health. Admin UI
|
||
encryption status indicator in Settings tab.
|
||
- **Per-chat model persistence.** Server-side `channels.settings`
|
||
JSONB field stores `last_selector_id`. Roams across devices with
|
||
localStorage write-through cache as fallback.
|
||
|
||
## [0.11.0] — 2026-02-25
|
||
|
||
### Added
|
||
- **Browser extension system.** Full lifecycle: manifest registration, script
|
||
injection, scoped context (`ctx.renderers`, `ctx.tools`, `ctx.events`,
|
||
`ctx.storage`, `ctx.ui`), permission-aware API proxying. Extensions self-
|
||
register via `Extensions.register()` and receive isolated contexts during
|
||
`initAll()`. Admin CRUD endpoints, asset serving (public, no auth needed
|
||
for `<script>` tag loading), auto-seeder for builtin extensions.
|
||
- **Custom renderer pipeline.** Block renderers intercept code fences by
|
||
language tag, post renderers process the DOM after insertion. Case-
|
||
insensitive language matching handles LLM capitalization (`Mermaid`,
|
||
`Diff`, `CSV`). Nested ` ```markdown ``` ` fence unwrapping prevents the
|
||
common LLM pattern of wrapping responses in markdown fences from breaking
|
||
inner code blocks.
|
||
- **Browser tool bridge.** Extensions register tool handlers via
|
||
`ctx.tools.handle()`. Server collects browser tool schemas alongside
|
||
server tools, includes them in LLM completions. Tool calls route through
|
||
WebSocket EventBus (`tool.call.*` → browser → `tool.result.*` → server).
|
||
30-second timeout with error recovery.
|
||
- **Server tools: calculator and datetime.** Auto-register via `init()`,
|
||
zero wiring changes. Calculator: recursive-descent evaluator with
|
||
arithmetic, 17 math functions, constants. Datetime: current date/time/
|
||
timezone/unix/ISO-week in any IANA timezone.
|
||
- **6 built-in browser extensions** (self-contained, own CSS via
|
||
`_injectStyles()`/`destroy()` lifecycle):
|
||
- **Mermaid**: block + post renderer, SVG diagrams, dark mode detection,
|
||
local vendor with CDN fallback, max-height constraint with scroll
|
||
- **KaTeX**: block renderer (` ```latex/math/tex ```) + post renderer
|
||
(inline `$...$` and `$$...$$` in text nodes)
|
||
- **CSV Table**: block renderer, RFC 4180 parser with quoted fields,
|
||
sortable columns (click headers), numeric-aware sorting
|
||
- **Diff Viewer**: block renderer, red/green syntax highlighting for
|
||
unified diffs, stats badge (+N/-N), hunk/file headers
|
||
- **JS Sandbox**: browser tool (`js_eval`), sandboxed iframe
|
||
(`allow-scripts` only), console capture, 10s timeout
|
||
- **Regex Tester**: browser tool (`regex_test`), multi-input matching,
|
||
full match details with named groups and indices
|
||
- **Admin extension editor.** Edit button on each extension in Admin →
|
||
Extensions. Inline editor shows name, description, manifest JSON, and
|
||
script source with tab-key support. System extensions show overwrite
|
||
warning.
|
||
- **Enhanced diagnostics.** Test 5: browser extensions (loaded, active,
|
||
renderers, tool handlers). Test 6: Service Worker cache (registration,
|
||
scope, state, cache names, entry counts). Purge Cache button in Debug
|
||
Log modal footer.
|
||
- **Extension-owned styles.** All 4 rendering extensions (mermaid, katex,
|
||
csv, diff) inject their own `<style>` tags during `init()` and remove
|
||
them during `destroy()`. Global stylesheet only keeps `.ext-rendered`
|
||
wrapper. User-created extensions can bring their own CSS without
|
||
touching the global stylesheet.
|
||
- **Notes extension rendering.** `runExtensionPostRender()` called in both
|
||
note read mode and preview mode. Mermaid diagrams and KaTeX math now
|
||
render correctly in notes.
|
||
- **Database migration 006_extensions.sql.** `extensions` and
|
||
`extension_user_settings` tables.
|
||
|
||
### Fixed
|
||
- **WebSocket token field mismatch.** API saved `{ accessToken: '...' }`
|
||
but EventBus read `tokens.access`. Tool bridge was dead on arrival.
|
||
- **Extension asset 401.** Asset route was behind auth middleware, but
|
||
`<script>` tags don't send Authorization headers. Moved to public group.
|
||
- **`runExtensionPostRender is not defined`.** Stale Service Worker cache
|
||
served old `ui-format.js` without the function. Added `typeof` guard.
|
||
- **Extension init order.** Extensions loaded after `loadChats()` — block
|
||
renderers not registered when messages first rendered. Moved extension
|
||
loading before chat loading.
|
||
- **K8s Ingress WebSocket routing.** Traefik `pathType: Exact` doesn't
|
||
reliably win over `Prefix` rules in the same Ingress resource. Changed
|
||
`/ws` and `/health` to `Prefix` for longest-prefix-wins routing.
|
||
- **Mermaid SVG oversized.** Added `max-height: 600px` on diagram
|
||
container with overflow scroll, removed mermaid.js hardcoded `height`
|
||
attribute from SVGs so CSS constraints apply.
|
||
|
||
### Changed
|
||
- Extension CSS removed from global `styles.css`. Each extension owns its
|
||
styles via `_injectStyles()` in `init()` with `destroy()` cleanup.
|
||
Idempotent injection guards prevent duplicates.
|
||
- Markdown fence language extraction lowercased at the point of extraction
|
||
in `ui-format.js`, making all block renderer pattern matches
|
||
case-insensitive without per-extension workarounds.
|
||
- `_unwrapMarkdownFence()` pre-processor strips outer ` ```markdown ``` `
|
||
wrappers when they contain nested fences. Handles think-block
|
||
placeholders and trailing explanation text. Only triggers when nested
|
||
fences are present — plain markdown code blocks still render normally.
|
||
|
||
## [0.10.5] — 2026-02-24
|
||
|
||
### Added
|
||
- **`ui-primitives.js` — shared rendering primitives and registries.**
|
||
Single source of truth for provider types, role definitions, and reusable
|
||
UI components. Extension-ready via registry pattern (`Providers.add()`,
|
||
`Roles.add()`). Primitives follow the `renderPresetForm()` pattern:
|
||
`(container, options) → control object` with `getValues/setValues/clear`.
|
||
- `Providers` registry — types, labels, default endpoints (was 5 places → 1)
|
||
- `Roles` registry — names, type filters, hints (was hardcoded in 2 places → 1)
|
||
- `renderCapBadges(caps, opts)` — consolidates 3 badge builders (compact + detailed)
|
||
- `renderProviderForm(container, opts)` — replaces 3 form implementations
|
||
- `renderProviderList(container, opts)` — replaces 3 list renderers
|
||
- `renderRoleConfig(container, opts)` — replaces 2 role UIs + 4 handlers
|
||
- `renderUsageDashboard(container, opts)` — replaces 3 usage renderers
|
||
|
||
### Fixed
|
||
- **Admin provider form now auto-fills endpoint on type change.** Was missing
|
||
from admin (worked in user BYOK and team forms). Now all three scopes use
|
||
the same primitive with identical behavior.
|
||
- **Team provider form consolidated.** Was two separate HTML forms (create +
|
||
edit) with separate listeners. Now a single dual-mode form matching the
|
||
pattern used by admin and user BYOK scopes.
|
||
|
||
### Changed
|
||
- Provider type definitions removed from `index.html` (2 static `<select>`s)
|
||
and `settings-handlers.js` (1 dynamic build + 2 endpoint maps). All now
|
||
sourced from `Providers` registry in `ui-primitives.js`.
|
||
- Provider list rendering uses event delegation instead of inline `onclick`
|
||
handlers. Each list returns `{ refresh, getCache }` control handles.
|
||
- Role configuration uses `data-role-*` attributes for event delegation
|
||
instead of `id`-based selectors and global `onchange` handlers.
|
||
- Usage dashboards accept options for compact/full mode, custom API calls,
|
||
and extension-provided extra columns.
|
||
- All 16 `confirm()` calls replaced with `showConfirm()` — styled modal
|
||
dialog matching the app design instead of browser-native dialog. Supports
|
||
`danger` styling, Escape/Enter keys, click-outside dismiss.
|
||
- Sidebar collapse icon now always visible (dimmed) next to the logo,
|
||
brightens on hover. Previously the icon replaced the logo on hover only.
|
||
When sidebar is collapsed, only the collapse icon shows (logo hidden).
|
||
- New `.popup-menu` + `.popup-menu-item` shared CSS base for all dropdown
|
||
and flyout menus. `createPopupMenu(anchor, opts)` primitive available for
|
||
future menu creation with consistent behavior.
|
||
- Removed ~360 lines of duplicated code across `admin-handlers.js`,
|
||
`settings-handlers.js`, `ui-admin.js`, and `ui-settings.js`.
|
||
- Removed ~40 lines of static HTML form markup from `index.html`.
|
||
|
||
## [0.10.4] — 2026-02-24
|
||
|
||
### Added
|
||
- **`model_type` field across the full pipeline.** Models now carry a type
|
||
classification (`chat`, `embedding`, `image`) sourced from provider APIs
|
||
at sync time — no hardcoded lists. Venice's `/v1/models` returns `type`
|
||
per model; OpenAI-compatible APIs pass through the field when present.
|
||
- New DB column: `model_catalog.model_type VARCHAR(20) DEFAULT 'chat'`
|
||
- Migration: `005_model_type.sql`
|
||
- Propagation: `providers.Model.Type` → `CatalogSyncEntry.ModelType` →
|
||
`CatalogEntry.ModelType` → `UserModel.ModelType` → frontend `model_type`
|
||
|
||
### Fixed
|
||
- **Admin role save didn't refresh UI.** `adminSaveRole()` showed "✓ Saved"
|
||
but never called `UI.loadAdminRoles()`, so dropdowns appeared stale after
|
||
save. Now reloads the roles panel after a successful save.
|
||
- **Role model dropdowns showed all models regardless of type.** Embedding
|
||
role showed chat models, utility role showed embedding models. Both admin
|
||
and user role UIs now filter the model dropdown by `model_type`:
|
||
- "embedding" role → only `model_type === 'embedding'` models
|
||
- "utility" role → only `model_type === 'chat'` models
|
||
|
||
### Changed
|
||
- Venice provider now reads the `type` field from each model in the API
|
||
response and normalizes it (`text` → `chat`, `embedding` → `embedding`,
|
||
`image` → `image`).
|
||
- OpenAI provider wire type extended with optional `type` field for
|
||
OpenAI-compatible APIs that include it.
|
||
|
||
## [0.10.3] — 2026-02-24
|
||
|
||
### Changed
|
||
- **Frontend refactor: 2 monolith files → 13 domain-scoped files.** Split
|
||
`ui.js` (2,582 lines) and `app.js` (2,940 lines) into 13 focused files
|
||
averaging ~544 lines each. No features added, no functions renamed, no
|
||
architectural changes. Vanilla JS, no modules, no build step.
|
||
|
||
**New file structure:**
|
||
|
||
| File | Lines | Domain |
|
||
|------|------:|--------|
|
||
| ui-format.js | 353 | Markdown rendering, `esc()`, code blocks, side panel |
|
||
| ui-core.js | 974 | UI object: sidebar, chat list, messages, streaming, model selector |
|
||
| ui-settings.js | 640 | Settings tabs, teams, providers, user preferences |
|
||
| ui-admin.js | 645 | Admin tabs, users, roles, usage, teams |
|
||
| tokens.js | 123 | Context tracking, token estimation |
|
||
| notes.js | 364 | Notes panel, editor, multi-select |
|
||
| chat.js | 584 | Chat ops, send, regen, edit, branch, summarize |
|
||
| settings-handlers.js | 692 | Settings save, provider CRUD, command palette |
|
||
| admin-handlers.js | 652 | Admin actions, presets, team management |
|
||
| app.js | 567 | State, init, boot, auth, listener dispatch |
|
||
|
||
Unchanged: `api.js` (575), `debug.js` (580), `events.js` (327).
|
||
|
||
- **`initListeners()` decomposed** into domain-specific init functions:
|
||
`_initChatListeners()`, `_initSettingsListeners()`, `_initAdminListeners()`,
|
||
`_initNotesListeners()`, `_initGlobalKeyboard()`. The orchestrator in `app.js`
|
||
dispatches to each.
|
||
- **Side panel resize** changed from self-invoking IIFE to `_initSidePanelResize()`
|
||
called during listener init, avoiding DOM timing issues.
|
||
- **Service worker** updated with new file list for pre-caching.
|
||
- **Policy-gating tests** updated to read from the correct source files after
|
||
the split. All 159 tests pass.
|
||
|
||
## [0.10.2] — 2026-02-24
|
||
|
||
### Added
|
||
- **Summarize & Continue** — User-triggered conversation compaction using the
|
||
utility model role. Button appears in context warning bar at ≥75% context
|
||
usage. `POST /channels/:id/summarize` calls the utility role to generate a
|
||
summary, inserts it as a tree node with `metadata.type = "summary"`, and
|
||
updates the cursor. Subsequent completions use the summary as a context
|
||
boundary — messages before it are replaced by the summary as a system message.
|
||
Multiple summaries stack. Fork-aware (summaries are tree nodes with their own
|
||
branch position). "Show full history" toggle reveals collapsed earlier messages.
|
||
- **BYOK Role Overrides** — Users with personal providers can override the org's
|
||
utility and embedding model roles. Resolution chain: personal → team → global.
|
||
New "Model Roles" tab in Settings (visible when BYOK is enabled). Stored in
|
||
`user.settings` JSONB under `model_roles` key, same shape as team overrides.
|
||
- **Utility Rate Limiting** — `utility_rate_limit` global setting (default: 20
|
||
calls/hour/user, 0 = unlimited). Org-funded utility calls check against
|
||
`usage_log` before executing. BYOK calls exempt (user pays their own way).
|
||
Returns 429 with clear message when exceeded.
|
||
- **Message metadata in path** — `PathMessage` now includes `metadata` JSONB
|
||
field, enabling summary detection and future message-type extensibility.
|
||
- **Per-chat model/preset restore** — Switching between chats now restores the
|
||
last-used model or preset in the selector. Stored in localStorage keyed by
|
||
channel ID. Falls back to the channel's base model, then the global default,
|
||
if the original selection is no longer available. Cleaned up on chat deletion.
|
||
|
||
### Changed
|
||
- **Generation role removed** — `RoleGeneration` ("generation") removed from
|
||
`ValidRoles` and admin Roles tab. Image/media generation will be
|
||
extension-managed (v0.11.0) with its own provider config, not a role slot.
|
||
The current completion/embedding abstraction doesn't fit image gen's
|
||
fundamentally different API surface.
|
||
- **Resolver.Complete/Embed signatures** — Now accept `userID` parameter for
|
||
personal role override resolution. Empty string skips personal lookup.
|
||
- **Proactive token refresh** — Access token is now refreshed at 80% of its
|
||
lifetime (~12min for 15min tokens). On page reload, a conservative 60s refresh
|
||
is scheduled since token age is unknown. Eliminates the race condition where
|
||
profile succeeds on a near-expired token but subsequent calls fail.
|
||
- **Auth guard in `startApp()`** — If token is invalidated during startup
|
||
(e.g., 401 on loadChats after profile succeeded), app returns to login
|
||
instead of rendering a broken UI with cascading 401 errors.
|
||
- **Per-chat model restore fallback** — When the stored model/preset is removed,
|
||
falls back to admin default → first visible model (was keeping stale selection).
|
||
Also cleans up the stale localStorage entry.
|
||
- **BYOK Role Overrides** — Fixed response parsing (`configs.configs` not
|
||
`configs.data`) and model field mapping (`configId`/`baseModelId` not
|
||
`provider_config_id`/`model_id`).
|
||
- **`GetRole` endpoint integration test** — `GET /admin/roles/:role` now
|
||
exercised by CI via `TestIntegration_Roles_GetSingleRole`, catching the
|
||
`GetConfig` signature mismatch that caused the build failure.
|
||
- **Auth resilience frontend tests** — 10 tests covering startup auth guard,
|
||
token refresh lifecycle, and the profile-success-then-401 edge case.
|
||
- **JS syntax lint in CI** — `node --check` runs on all `src/js/*.js` files
|
||
before frontend tests, catching parse errors that tests alone can't detect.
|
||
|
||
## [0.10.1] — 2026-02-24
|
||
|
||
### Added
|
||
- **Admin System Prompt** — Global system prompt configured in Admin › Settings.
|
||
Injected as the first system message in every conversation, before user/preset
|
||
system prompts. Users cannot override or disable it. Stored in
|
||
`global_settings['system_prompt']`.
|
||
- **Personas tab** — User presets moved from the Models tab to their own
|
||
"Personas" tab in Settings. Tab is hidden when admin disables user presets
|
||
(`allow_user_personas` policy).
|
||
- **Team Management modal** — Team admin functionality extracted from Settings
|
||
into its own tabbed modal (Members, Providers, Presets, Usage, Activity).
|
||
Accessed via "Team Management" flyout menu item, or "Manage →" on team cards
|
||
in Settings. Multi-team admins see a picker first; single-team admins go
|
||
straight to the tabbed view. Back arrow in header returns to picker.
|
||
- **Preview pane: clear button** — Trash icon in the preview header resets the
|
||
iframe and shows the empty hint. Also auto-clears when deleting a chat that
|
||
had active preview content.
|
||
- **Preview/Notes pane: fullscreen** — Expand button in the header toggles
|
||
fullscreen mode (panel fills entire viewport width).
|
||
- **Preview/Notes pane: resizable** — Drag the left edge of the side panel
|
||
to resize (280px–70vw). Width resets on close.
|
||
- **Code block: download** — "Download" button on every code block. Infers
|
||
file extension from language tag (e.g. `python` → `.py`, `go` → `.go`).
|
||
|
||
### Changed
|
||
- **Personal Usage scoped to BYOK** — `GET /usage` and the user Usage tab now
|
||
only show consumption against personal (BYOK) providers. Global provider
|
||
usage is the org's cost, not the user's. New `QueryByUserPersonal` store
|
||
method filters on `scope = 'personal' AND owner_id = user_id`. Admin usage
|
||
views remain unaffected. Usage tab hidden when admin disables user providers
|
||
(`allow_user_byok` policy).
|
||
- **OpenAI streaming usage race** — Deferred the Done event until the usage
|
||
chunk arrives. Previously, finish_reason fired Done before the usage chunk
|
||
(which has `choices: []`), so streaming token counts were always 0 for all
|
||
OpenAI-compatible providers.
|
||
|
||
### Fixed
|
||
- **Usage logging zero-token guard** — Removed early exit in `logUsage` that
|
||
suppressed rows when tokens were 0. Combined with the streaming race above,
|
||
this meant no streaming usage was ever recorded.
|
||
- **Live chat completion test** — `TestLive_VeniceChatCompletion` sent wrong
|
||
field names (`messages`/`config_id` instead of `content`/`provider_config_id`).
|
||
|
||
## [0.10.0] — 2026-02-24
|
||
|
||
### Added
|
||
- **Model Roles** — Named model slots (`utility`, `embedding`, `generation`)
|
||
with primary + fallback bindings. Stored in `global_settings['model_roles']`.
|
||
Team-level overrides via `teams.settings` JSONB. New `server/roles/` package
|
||
with `Resolver.Complete()` and `Resolver.Embed()` for automatic failover.
|
||
Admin API: `GET/PUT /admin/roles/:role`, `POST /admin/roles/:role/test`.
|
||
Team API: `GET/PUT/DELETE /teams/:id/roles/:role`.
|
||
- **Provider Embed() interface** — All providers implement `Embed()`. OpenAI,
|
||
Venice, OpenRouter support `/v1/embeddings`; Anthropic returns `ErrNotSupported`.
|
||
New types: `EmbeddingRequest`, `EmbeddingResponse`.
|
||
- **Usage Tracking** — Every completion (streaming and non-streaming) logs
|
||
token counts and cost to `usage_log` table. Cost calculated at insert time
|
||
from `model_pricing` (no retroactive recalculation). Provider scope
|
||
denormalized for efficient admin filtering. Admin views exclude BYOK.
|
||
New stores: `UsageStore`, `PricingStore`. Admin API:
|
||
`GET /admin/usage`, `GET /admin/usage/users/:id`, `GET /admin/usage/teams/:id`.
|
||
User API: `GET /usage` (personal summary).
|
||
- **Model Pricing** — `model_pricing` table with catalog sync and manual admin
|
||
overrides. Sync from provider APIs (Venice, OpenRouter) auto-populates
|
||
pricing with `source='catalog'`; manual entries never overwritten. Admin API:
|
||
`GET/PUT/DELETE /admin/pricing`.
|
||
- **Streaming token capture** — OpenAI: `stream_options.include_usage` +
|
||
parse usage/cache from final chunk. Anthropic: parse `message_start` for
|
||
input/cache tokens, `message_delta` for output tokens. Cache token fields
|
||
(`CacheCreationTokens`, `CacheReadTokens`) on `CompletionResponse`,
|
||
`StreamEvent`, and `streamResult`.
|
||
- **Admin Roles tab** — Configure primary/fallback provider+model per role,
|
||
test-fire from UI.
|
||
- **Admin Usage dashboard** — Period selector (7d/30d/90d), group-by
|
||
(model/user/day/provider), totals cards, breakdown table, pricing table.
|
||
- **Team Usage dashboard** — Team admins can view usage against their
|
||
team-owned providers via `GET /teams/:teamId/usage`. Filters to
|
||
`provider_configs WHERE scope='team' AND owner_id=teamId`. Integrated
|
||
into team management panel with period/group-by selectors.
|
||
- **Admin Reset Password** — Button in user list with vault destruction
|
||
warning dialog (two-step confirmation).
|
||
- **User Usage tab** — Settings modal "Usage" tab shows personal token
|
||
consumption and estimated costs across all conversations, including BYOK.
|
||
Period selector (7d/30d/90d) and group-by (model/day).
|
||
- **Live Venice integration tests** — Gated behind `VENICE_API_KEY` env var.
|
||
Tests: non-streaming completion, streaming completion, usage logging for
|
||
both modes, pricing from catalog sync, and direct embeddings via BGE-M3.
|
||
Uses `qwen3-4b` (Venice Small) — cheapest at $0.05/$0.15 per 1M tokens.
|
||
- **Migration 004** — `usage_log` table, `model_pricing` table, `model_roles`
|
||
seed in `global_settings`.
|
||
|
||
### Fixed
|
||
- **UEK re-wrap on password change** — `ChangePassword` now decrypts UEK
|
||
with old password and re-encrypts with new password + fresh salt. Previously,
|
||
password changes silently broke all personal BYOK keys.
|
||
- **UEK destruction on admin password reset** — `ResetPassword` now nullifies
|
||
vault columns, evicts UEK from cache, deletes personal provider configs,
|
||
and logs an audit event. Previously, admin resets left orphaned encrypted
|
||
UEK that silently broke personal keys.
|
||
- **Streaming completions logged zero tokens** — `StreamEvent` lacked token
|
||
fields and providers discarded usage data from final chunks. Both OpenAI
|
||
and Anthropic streaming parsers now capture and propagate token counts.
|
||
- **OpenAI streaming usage race condition** — The OpenAI protocol sends
|
||
chunks in order: content → finish_reason → usage → [DONE]. The parser
|
||
sent `Done=true` on the finish_reason chunk (step 2) before the usage
|
||
chunk arrived (step 3, with `choices:[]`). The receiver returned
|
||
immediately on Done, so usage was captured but never delivered.
|
||
Fix: defer the Done event as `pendingFinish` until the usage chunk
|
||
arrives, then flush with tokens attached. Tool-call finishes flush
|
||
immediately (tool loop needs the event). Affects all OpenAI-compatible
|
||
providers (OpenAI, Venice, OpenRouter).
|
||
- **Token accumulation across tool iterations** — `streamResult` in
|
||
`stream_loop.go` now accumulates input/output/cache tokens across
|
||
multi-tool-call iterations instead of only capturing the final iteration.
|
||
- **Admin pricing leaked BYOK entries** — `PricingStore.List()` returned
|
||
all model_pricing rows including those from personal BYOK providers.
|
||
Admin panel showed pricing entries for user-private providers they
|
||
shouldn't see, with O(users × models) scale explosion. Now joins on
|
||
`provider_configs` and filters `scope != 'personal'`. Admin `UpsertPricing`
|
||
also validates provider scope, rejecting manual pricing on personal
|
||
providers.
|
||
- **Team role handlers used wrong param name** — `ListTeamRoles`,
|
||
`UpdateTeamRole`, `DeleteTeamRole` read `c.Param("id")` but routes use
|
||
`:teamId`. Every team role operation silently got an empty team ID.
|
||
- **Usage logging suppressed for zero-token streams** — `logUsage` had an
|
||
early exit when `inputTokens == 0 && outputTokens == 0`. Combined with the
|
||
OpenAI streaming parser bug (below), this silently dropped all streaming
|
||
usage rows. Removed the guard — requests are always recorded.
|
||
- **Live chat completion test used wrong field names** — `TestLive_VeniceChatCompletion`
|
||
sent `messages` and `config_id` but the handler expects `content` and
|
||
`provider_config_id`. Test silently skipped on the binding error.
|
||
|
||
## [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`) |