Repository navigation
Add offline configuration migration and stream reasoning in realtime mode - #66
Merged
Merged
Conversation
Three deployment concerns had drifted apart from how the project is actually run: images published extra floating tags, Compose repeated runtime settings as per-variable fallbacks, and requirements.in existed only to mirror pyproject. - .github/workflows/docker.yml publishes exactly the release version, `edge` for main and `latest` for release tags; PR and sha tags are gone, so pulled tags always name a supported build. - docker-compose.yml loads the whole .env through env_file and fixes only the container paths, so a runtime key is configured once and a commented line stays WebUI-editable instead of being locked by a fallback default. The image defaults to `latest`, and the image passes --desensitize itself. - requirements.in is removed: pyproject.toml is the single source for direct dependencies, and the build-lock test compares uv.lock against it directly. - tests/test_deployment.py follows the new Compose shape and the image-tag test pins the three published tags.
Narrow layouts clipped interactive content: the settings tab row pushed the page 16px sideways at 390px, a credential row menu opened inside the scrolling table wrapper and disappeared below it, the expanded trend data table was cut off, and the mobile navigation hid its last entries behind an unmarked scroll area. - The row menu renders through a portal with viewport-clamped fixed coordinates, repositioning on scroll and resize; a panel's backdrop-filter otherwise made a locally fixed menu resolve against the panel and leave the screen. - `.tabs` and the mobile nav wrap instead of overflowing, `.chartData` scrolls its expanded table, and selects cap their width at the container. - web/e2e/layout.spec.ts locks both regressions: no horizontal page or tab overflow at 390px, and the last row menu stays portaled and fully visible.
A refresh token plus its endpoint identifies an account, but the gateway only accepted a complete .info file, so users holding a single refresh token could not enrol an account at all. - auth_oauth.refresh_tokens exchanges one token without any access token, reads the account uid from the JWT subject, refreshes against the selected profile and fetches the account block so the tenant is preserved for enterprise accounts; a uid mismatch is rejected. - POST /admin/credentials/refresh-import accepts up to 50 tokens behind the existing admin authentication and a shared lock, validates the endpoint, skips duplicates, reports per-token failures and stores successes through the normal credential writer. - The import drawer gains a refresh-token tab with an endpoint selector and one token per line, reusing the existing result list; prepareImports parsing is covered by imports.test.ts and the API/UI flow by refreshImport.test.tsx.
Realtime was named for its output cadence but still buffered every reasoning delta until text, a tool call or the finish marker appeared, so clients saw a long silent period and then one oversized thinking event. That hid the real first-token time and inflated client-side TPS, and it made stream_mode=realtime misleading for reasoning models. - _stream_upstream and _stream_adapted only wrap the Chat SSE stream with _coalesce_reasoning_sse when the request is not realtime; realtime forwards each reasoning delta as it arrives. - Responses and Anthropic adapters already keep one logical thinking output item/block and append incremental deltas, so no client sees N separate thoughts and no adapter change is needed. - compatible keeps the historical single-block replay for clients that depend on it; the wrapping helper's docstring now says so. - tests/test_realtime_streaming.py splits the old coalescing assertion into a protocol matrix: realtime yields three Chat/reasoning events, three Responses summary deltas on one output item and three Anthropic thinking deltas on one block, while compatible still emits one coalesced reasoning event.
Runtime settings could be supplied by .env, the process environment or the CLI with no way to move them into the configuration store, so every such key stayed read-only in the WebUI and the same value lived in three places. The WebUI also had no shared interface preference, and instances had no way to tell offline tools to keep their hands off a live database. - `converter.py config migrate|status` resolves the env file first and then the data directory it declares, accepts --env-file/--no-env/--data-dir/--db and refuses to touch a database that is not this project's control store (SQLite header, user_version=2, required tables, payload, quick_check). Migration writes runtime-scoped values and config_notice_muted in one revision; status never writes. - A running instance claims a service_lease heartbeat, so migrate refuses while the database is in use unless --force is given; both paths share the same read-only identity check. - SCHEMA records env_scope and lockable: deployment keys stay authoritative, language is a hot preference with no environment mapping, and hidden config_notice_muted records the one-time notice state. - The Dashboard shows a banner listing runtime keys still supplied by the environment, with a copyable migration command and a confirmation drawer before muting; either migration or dismissal stops the notice permanently. - `--lang zh|en|auto` writes the interface language on serve/login/config migrate, and the WebUI language preference now round-trips through the store instead of staying browser-local; config status only uses --lang for its own output.
|
Sorry @maiphucgiang, your pull request is larger than the review limit of 150,000 diff characters |
Removing every PR tag left metadata-action with nothing to name, so the image job warned that no tag or version was generated on pull requests. PR builds are still useful as validation, so give them an unpublished ref tag while the three published tags stay unchanged.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Configuration store and offline migration
converter.py config migrate|statusmoves runtime settings from.envor the process environment intocontrol.sqlite3without editing the user's file. It resolves the env file first, then the data directory that file declares (CODEBUDDY_AUTH_DIR), and accepts--env-file,--no-env,--data-dir,--db,--dry-run,--keysand--force.user_version=2,control/runtime_state/gateway_secrets, readablepayload.settingsandquick_check. A missing path, a foreign SQLite file, the auditlogs.sqlite3, an older schema or a corrupt database is refused without creating or modifying anything.--keysare rejected, and a value that already matches the store does not bump the revision.config statusshares the same resolution and identity code and never writes.service_leaseheartbeat every 30s (stale after 90s);migratealso probesBEGIN IMMEDIATEbefore writing. Either signal refuses the run with the live pid unless--forceis given. Lease release is installed as a SIGTERM/SIGINT handler because uvicorn restores the default handler and re-raises the signal, so afinallyblock never runs.config_notice_mutedcommit in one revision throughControlStore.update_settings, so a failed validation or a revision race leaves both untouched.SCHEMAnow recordsenv_scope(deployment/runtime/ui/none) andlockable: deployment keys stay authoritative through.env/CLI,languageis a hot preference with no environment mapping and is never locked, and the hiddenconfig_notice_mutedstores the one-time notice state.Dashboard notice
GET /admin/dashboardreports runtime keys still supplied by the environment; the banner lists them with a copyablepython3 converter.py config migratecommand and a documentation link.POST /admin/config-notice/dismiss(admin auth + CSRF, idempotent, writes aconfig.notice_dismissedaudit event). Migrating or dismissing sets the same flag, so later.envadditions never re-open the notice.Interface language
serverPreferencemapsauto/unknown to the system language.--lang zh|en|autowrites the interface language onserve,loginandconfig migrate;config statusis read-only and uses--langonly for its own output. A newapp/i18n.pyprovides the zh/en CLI catalog.Refresh-token import
POST /admin/credentials/refresh-importaccepts 1–50 tokens behind the existing admin authentication and a shared lock, validates the endpoint, skips duplicates and reports per-token failures.auth_oauth.refresh_tokensexchanges a token without any access token, reads the uid from the JWT subject, refreshes against the selected profile and fetches the account block so enterprise tenants survive; a uid mismatch is rejected. Successes go through the normal credential writer.Realtime reasoning
realtimenow forwards reasoning deltas as they arrive;compatiblekeeps the historical single-block replay. Responses and Anthropic still emit one logical thinking item/block with incremental deltas.Layout
position: fixedresolved against a backdrop-filtered panel and left the screen.e2e/layout.spec.tslocks the 390px behaviour.Deployment and images
edgefrommainandlatestfrom release tags; pull-request builds keep an unpublishedtype=ref,event=prtag so the build still has an identity.docker-compose.ymlloads the whole.envthroughenv_filewithrequired: false, so a missing file still boots from store settings; onlyCODEBUDDY_AUTH_DIRandCODEBUDDY2API_ALLOW_OPEN_NOAUTHremain fixed, and the default image tag islatest.--desensitize;docs/clients.mdand its Chinese counterpart follow.requirements.inis removed:pyproject.tomlis the single source for direct dependencies and the build-lock test comparesuv.lockagainst it..env.examplemigration note, deployment (host and container commands), advanced configuration precedence, WebUI and clients pages.Commits
63ca33cSimplify image tags, Compose environment and dependency locks84b1fb4Keep menus and tab rows inside the viewport9613159Import credentials from refresh tokens66e5205Stream reasoning in realtime modeff54647Add offline configuration migration for runtime settings4a0677eKeep a build-only tag on pull requestsValidation
tests/test_*.pyfile exits 0 (70 files), including the newtests/test_config_migration.py(target identity, lease refusal, dry-run, key filters, language transaction, invalid-value atomicity) and updatedtest_environment_config.py,test_admin_api.py,test_identity_sync.py.vp test run19 files / 153 tests, includingconfigNotice.test.tsxandrefreshImport.test.tsx;vp checkformatting, lint and type checks clean; full Playwright suite 18 passed including the newe2e/layout.spec.ts.config statusreported the running lease;config migratewas refused while the service was up; after stopping, migration wrotemax_images/stream_mode, kepthostin.env, and the WebUI showedsource=management,locked=false.deepseek-v4.1-flashandstream_mode=realtime: 73 reasoning deltas starting at 1.741s, then 38 content deltas starting at 2.263s (previously one reasoning block arrived with the first content).--lang ensavedlanguage=enwithout locking it; a later WebUI switch back succeeded;config status --langonly changed that invocation's output.