Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
30e79d0
feat(gui): manage Aside sync per profile with scoped recovery
invalid-email-address Sep 5, 2026
8e726e0
test(responses): retain dotted namespace alias in compaction inventory
invalid-email-address Sep 5, 2026
db7a3c3
test(aside): align aggregate status and privacy-safe fixtures
invalid-email-address Sep 5, 2026
9239757
fix(aside): register profile routes and CLI surface metadata
invalid-email-address Sep 5, 2026
473782c
Merge branch 'codex/aside-profile-controls-5598' into codex/aside-pro…
invalid-email-address Sep 5, 2026
9e3a179
fix(responses): compare duplicate repair schemas structurally
invalid-email-address Sep 5, 2026
62cf49c
docs(aside): explain server-owned synchronization requirements
invalid-email-address Sep 5, 2026
70be638
fix(gui): report partial client refresh after model selection
invalid-email-address Sep 5, 2026
576e6b5
Merge branch 'codex/aside-profile-controls-5598' into codex/aside-pro…
invalid-email-address Sep 5, 2026
d1fb2c7
fix(i18n): retain Aside profile paths in ownership guidance
invalid-email-address Sep 5, 2026
24f62f3
docs(integrations): describe profile routes and partial refresh outcomes
invalid-email-address Sep 5, 2026
06397f0
fix(responses): reject mismatched namespace call kinds
invalid-email-address Sep 5, 2026
3d95531
fix(cli): report Aside sync when the proxy is unavailable
invalid-email-address Sep 5, 2026
55d6634
Merge branch 'codex/aside-profile-controls-5598' into codex/aside-pro…
invalid-email-address Sep 5, 2026
89666ae
fix(clients): reload selection after initial model discovery
invalid-email-address Sep 5, 2026
bbd9a21
fix(responses): preserve original custom kind through namespace lowering
invalid-email-address Sep 5, 2026
5d8254f
docs(plan): clarify explicit client refresh ownership
invalid-email-address Sep 5, 2026
1e5acbd
test(responses): keep replay credentials stable across clock ticks
invalid-email-address Sep 5, 2026
7bcccc7
Merge branch 'codex/aside-profile-controls-5598' into codex/aside-pro…
invalid-email-address Sep 5, 2026
d8ac1e0
fix(aside): tighten profile sync and history contracts
invalid-email-address Sep 5, 2026
c0f14b7
fix(gui): reject inconsistent Aside aggregate outcomes
invalid-email-address Sep 5, 2026
25b8453
Merge branch 'codex/aside-profile-controls-5598' into codex/aside-pro…
invalid-email-address Sep 5, 2026
5dce8a0
Merge branch 'codex/aside-profile-controls-5598' into codex/aside-pro…
invalid-email-address Sep 5, 2026
bb9dd25
fix(gui): use stable identities for client refresh warnings
invalid-email-address Sep 5, 2026
8e87faa
ci: refresh pending GUI stack checks
invalid-email-address Sep 5, 2026
1979f5c
Merge branch 'codex/aside-profile-controls-5598' into codex/aside-pro…
invalid-email-address Sep 5, 2026
e637cd3
test(clients): bound asynchronous client state probes
invalid-email-address Sep 5, 2026
b754ffe
Merge branch 'codex/aside-profile-controls-5598' into codex/aside-pro…
invalid-email-address Sep 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions devlog/_plan/260906_aside_profiles/020_profiles_gui.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,21 @@ UPDATE guides/integrations.md and operating CLI docs with all-profile default, -
Verification: remote focused GUI/API tests, GUI lint/i18n/build, root typecheck and required CI. Browser QA on local dev UI against three synthetic profiles, never real user profile mutation: initial mixed state, global enable, one profile disable, return to list after details, correct request selector, reload retains off state, failed profile does not imply sibling success, keyboard switches and narrow viewport. Capture actual screenshot for PR body using existing browser plugin, view it, and fix layout if needed. Screenshot contains synthetic labels only. A PR mentioning GUI includes screenshot. No local test suite or local typecheck; local dev server/browser probes are permitted.

Terminal: verify every PR current head and all applicable hosted checks; native stack registration, owner-authorized admin merge, async merge completion, fetch dev and prove every merge SHA ancestry. Resolve CI or reviews rather than bypass evidence. No release or live service deployment. All original Grok/Pi/Codex and added Aside-profile criteria must be met before host goal completion.

## P revalidation at1d4da9f9b

Backend primary routes are now dedicated nested profile paths; server-only POST /aside/sync owns synchronization and preserves exclusions. The UI Sync-now button uses it, while the bulk switch sets all desired flags and per-row switches set one. Shared Switch already supports mixed state and aria-pressed. Preserve existing monochrome tokens/ClientMark (variance2/motion1/densityD5). StatusDTO carries desired enabled plus actual state, counts and per-profile errors; show partial/error states explicitly. FileIntegrationPage and RestoreDialog/Overview actions carry optional profile IDs into paths and cache keys.

Main owns AsideProfilesPage, integration-api profile DTO/functions, parentpage/FileIntegrationPage/RestoreDialog/Overview wiring and CSS. A disjoint locale/test worker may own gui/src/i18n/{en,de,fr,ko,zh,zh-TW,ru,ja,tr}.ts and new gui/tests/aside-profiles-page.test.tsx after exactkeys/APIcontract are fixed. All GUI checks run remotely; local Vite and browser probes only. Capture wide+narrow realcomponent screenshots against synthetic three-profile management fixtures, not real accounts.

A audit passed: DTO error precedes empty rendering; stale errors remain visible; always refetch after refused mutations because intent may already be saved; use mixed Switch and void refresh semantics. Exact locale keys are locked in ignored .tmp/aside-profiles/ui-keys.json. Main exports loadAsideProfiles and syncAsideProfiles from integration-api, and adds profileId as last optional argument to existing state/toggle/history/restore/delete functions. Toggle rejects207 partial only through returnedokfalse; UI reports it and refetches.

Implementation modularization: profile DTO validation and load/sync readers live in new aside-profile-api.ts, reusing the existing integration transport/error owner without a circular re-export. Existing integration-api functions keep their non-Aside signatures and unscoped cache identities; profile scope is an optional final argument. Scoped successful state/toggle/restore/delete responses and journal rows must match the requested profile.

Review fold-back: per-profile refusal/recovery outcomes remain typed and visible in bulk and Sync-now failures, including snapshotPath/residual; failed Aside restore reconciles owner resources while retaining the dialog/error; list and detail share one pure profile-status validator. Add a localized no-snapshot recovery warning and the corresponding regression cases. No source permission or confirmation boundary is weakened.

B verification: the final interface provides bulk and individual desired-state switches, actual applied counts, per-profile retry/refusal and recovery details, profile-scoped history/restore/delete, and nine-locale copy. Remote checks passed: 74 interface/API cases, then 54 affected interface/cache cases after type narrowing, GUI build and both lint commands; 27 engine cases with real failed compensation, 62 CLI cases, and root typecheck. Initial failing compensation fixture was corrected to fail ownership after a successful file write, then fail rollback. Browser probes on three synthetic profiles covered bulk enable, exclusion persistence, scoped Undo, keyboard control, narrow layout without horizontal overflow, and per-profile external-edit refusal. Screenshot: docs-site/public/screenshots/aside-profiles.jpg. No real Aside profile files were written. Hosted exact-head CI and stack landing remain open.

Hosted-review follow-up stays within terminal stabilization: Models selection/preset consumers must distinguish saved selection from refused client-file refresh. Add a persistent warning listing each failed client/profile and recovery details while retaining truthful selection-success feedback; clear it after a later successful refresh result. Failed HTTP saves never claim saved selection. Reuse shared refusal formatting and add nine-locale copy, focused interface cases and browser evidence. The ordinary owned-refresh producer also preserves backup/residual metadata. Native schema review follow-up uses structural object-key equality while retaining array order and true-conflict refusal; test both explicit and loaded duplicate declarations. These are review repairs to the existing stack slices, not new product scope.

Final review verification: model-warning fixes passed 35 remote interface cases, build and lint; 80 schema/owned-refresh cases passed. Full remote interface suite passed 1,484 cases with one ownership-path copy failure; the missing per-profile path was restored in all nine locale sentences, and the unchanged five-case locale-parity file, build and i18n lint then passed. No test was weakened. Browser evidence confirms a saved selection with an independent affected-profile warning and clears the warning after a subsequent successful refresh. Independent reviews passed for the interface recovery, registry reconciliation, model-warning concurrency/fallback behavior and structural schema comparator. A complete source hash manifest matched 576e6b557 for the final root typecheck and privacy scan. Hosted exact-head checks and merge ancestry remain terminal evidence to capture in the session ledger.
Binary file added docs-site/public/screenshots/aside-profiles.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
22 changes: 20 additions & 2 deletions docs-site/src/content/docs/guides/integrations.md
Original file line number Diff line number Diff line change
Expand Up @@ -218,12 +218,24 @@ ocx mcode

Once connected, `ocx sync` refreshes owned MCode, Pi, and Aside catalogs with the current
model selection, context windows, and reasoning-effort ladders. Changes to model visibility,
provider selection, or presets also refresh connected Pi and Aside catalogs. Missing,
foreign-edited, unsafe, and never-owned blocks stay untouched; reconnect them explicitly.
provider selection, or presets also refresh connected Pi and Aside catalogs. Foreign-edited
or unsafe blocks stay untouched, as do previously owned blocks you removed manually.
An enabled Aside profile is an exception to the usual owned-only refresh: if its account
directory exists and it has never had an owned block, sync may create its first block when
that slot is empty. A prior Aside connection enables this behavior for all registered
profiles by default. Sync does not create missing account directories or replace manual blocks.
A refused or overlapping refresh is reported separately for each client. Start a new Pi
session or fully quit and reopen Aside to load the updated file.
Aside refresh requires a [compatible running proxy](#aside-profile-controls).

If Models reports **“Model selection saved”** together with a client-refresh warning, the
selection is already saved; one or more client files could not be updated. The warning names
the affected client and Aside profile, when applicable, and explains the refusal. Open
**Integrations** to inspect that client or profile before starting a new session. Resolve the
reported issue, then retry `ocx sync`; an overlapping operation must finish first. If the
warning includes a backup path or says recovery did not finish, inspect that recovery state
before retrying. A successful selection save alone does not confirm client-file recovery.

The separate MiniMax platform CLI (`mmx`) is not a file-toggle integration. Its text
commands use MiniMax's Anthropic-compatible endpoint, so OpenCodex provides a
credential-isolated, loopback-only launcher:
Expand Down Expand Up @@ -274,6 +286,12 @@ refusal are reported for each profile. A partial bulk result is not an all-appli
the CLI exits nonzero. Undo restores the selected profile's synchronization intent as well as
its file, so a later sync does not silently reverse Undo.

The [profile API](/reference/management-api/#aside-profile-controls) returns HTTP 200 for a
successful bulk operation and HTTP 207 with `ok: false` if any profile refuses. Inspect every
entry in `results`: successful profiles are not rolled back when another fails. Desired
settings remain saved, so retry after addressing the affected profile rather than assuming
the entire change failed. If saving those settings fails, no profile files are changed.

Each profile has separate ownership and history. Existing user edits, unsafe paths and linked
catalogs are refused; the existing explicit overwrite and drift-confirmation controls remain
available. Fully quit and reopen Aside to load changed model files.
52 changes: 51 additions & 1 deletion docs-site/src/content/docs/reference/management-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,47 @@ For the concepts behind the model roster and encrypted worker-task behavior, see
| `DELETE /api/client-integrations/journal?opId=...` | Retire one older rollback operation and remove its snapshot when possible. Success returns `snapshotRemoved`; `false` means cleanup was retained for maintenance retry. | 400 missing `opId`; 404 missing or already retired operation; 409 newest operation for that client |

Deletion appends a tombstone instead of rewriting the journal. The newest operation for each client
is protected server-side so the current undo point remains available.
is protected server-side so the current undo point remains available. For Aside, protection is
per profile, and journal rows include `profileId`.

### Aside profile controls

Use these dedicated paths with a compatible running proxy. `{profileId}` is a registered
nonnegative integer account ID returned by the profile list; it is not a browser path.

| Method and path | Purpose | Notable errors |
| --- | --- | --- |
| `GET /api/client-integrations/aside/profiles` | List `profiles[]`, desired `enabledCount`/`allEnabled`, actual `appliedCount`, and `total` | HTTP 200 may contain an empty, unsafe aggregate with an `error` when discovery is unavailable |
| `PUT /api/client-integrations/aside/profiles` | Set every registered profile's desired state and apply it; body `{ "enabled": true }`, with optional `overwriteConflict` when enabling | 400 invalid body; 409 operation busy; 500 preference-save failure; 207 per-profile refusals |
| `GET /api/client-integrations/aside/profiles/{profileId}` | Read one profile's desired `enabled` and actual integration status | 400 invalid ID; 404 unregistered profile |
| `PUT /api/client-integrations/aside/profiles/{profileId}` | Change one profile with the same body as bulk PUT, leaving sibling preferences unchanged | 400 invalid body/ID; 404 unknown profile; 409 busy or refusal; 500 save/write failure |
| `GET /api/client-integrations/aside/profiles/journal` | List history across registered Aside profiles | Rows include `profileId`, snapshot availability, `undoable`, and `deletable` |
| `GET /api/client-integrations/aside/profiles/{profileId}/journal` | List one profile's history, including matching legacy operations | 400 invalid ID; 404 unknown profile |
| `DELETE /api/client-integrations/aside/profiles/journal?opId=...` | Retire an older operation, resolving its profile from history | 400 missing ID; 404 missing operation; 409 newest operation for its profile |
| `DELETE /api/client-integrations/aside/profiles/{profileId}/journal?opId=...` | Retire an older operation belonging to the selected profile | Same deletion errors; an operation cannot target a different profile |
| `POST /api/client-integrations/aside/profiles/{profileId}/restore` | Undo an operation using `{ "opId": "..." }`; optional `confirmDrift: true` permits replacing later edits | 404 missing operation/profile; 409 busy, mismatch, or required drift confirmation; 410 expired snapshot; 500 save/write failure |
| `POST /api/client-integrations/aside/sync` | Refresh enabled profiles through the server's mutation owner; body `{}` | 400 nonempty body/profile selector; 409 busy; 207 per-profile refusals |

Bulk PUT returns `{ ok, clientId, changed, state, message, results }`; each result identifies
its `profileId` and reports the writer outcome. Sync returns `{ ok, clientId, results }`, with
per-profile refresh outcomes. Both return HTTP 200 when all returned attempts succeed and
HTTP 207 with `ok: false` when any attempt refuses. HTTP 207 is a partial-result envelope,
including when every attempted profile refuses: inspect each result rather than treating a
2xx response as complete success. A successful no-op can have `changed: false`; sync does not
attempt disabled profiles. Single-profile writes and restores return HTTP 200 on success or
the corresponding error status on refusal.

Explicit changes save desired preferences before writing files. A preference-save failure
leaves profile files unchanged. A later file refusal preserves saved intent and successful
sibling writes; inspect the affected profile before retrying. Refusals may include
`snapshotPath` and `residual: true` when recovery did not finish. Restore also reconciles the
target profile's desired state, so the next sync does not reverse Undo. Deletion returns
`snapshotRemoved`; `false` means snapshot cleanup still needs maintenance.

The legacy `GET, PUT /api/client-integrations/aside` aliases remain available. New clients
should use the dedicated paths above so an older proxy cannot ignore a profile selector.
See [Aside profile controls](/guides/integrations/#aside-profile-controls) for CLI commands and
the proxy upgrade, restart, and retry sequence.

### Combos

Expand Down Expand Up @@ -206,6 +246,16 @@ manual model.

Valid PUT requests to `/api/selected-models` and `/api/model-presets` return HTTP 409 with code `initial_model_selection_pending` until a reliable initial model list is available. Refresh model discovery (for example, `GET /api/models`) and retry after it succeeds.

Successful visibility/selection writes to `/api/disabled-models`, `/api/model-visibility`,
`/api/selected-models`, and `/api/model-presets` report follow-up outcomes in `catalogRefresh`
and `clientIntegrations` when that refresh path runs. HTTP 200 and `ok: true` confirm the
selection save; they do not guarantee every client catalog updated. Inspect
`clientIntegrations[]` for `ok: false`, `client`, optional Aside `profileId`, and the refusal
`reason`; recovery details may also include `refusalReason`, `snapshotPath`, and `residual`.
The Models page keeps the saved selection and shows a separate client-refresh warning.
Inspect Integrations and resolve the reported issue before retrying `ocx sync`. Missing
outcome fields from an older server do not establish successful recovery.

### OAuth accounts, provider keys, and data-plane keys

| Method and path | Purpose | Notable errors |
Expand Down
19 changes: 18 additions & 1 deletion gui/src/i18n/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -601,6 +601,7 @@ export const de: Record<TKey, string> = {
"models.tipActive": "Aktiv",
"models.tipDisabled": "Deaktiviert",
"models.applied": "Angewendet — greift bei der nächsten Codex-Runde.",
"models.integrationRefreshWarning": "Modellauswahl gespeichert. Einige Client-Kataloge konnten nicht aktualisiert werden. Prüfe vor dem Start einer neuen Sitzung die Integrationen.",
"models.saveFailed": "Speichern fehlgeschlagen",
"models.networkError": "Netzwerkfehler — läuft der Proxy?",
"models.loadFail": "Modelle konnten nicht geladen werden — läuft der Proxy?",
Expand Down Expand Up @@ -1064,6 +1065,21 @@ export const de: Record<TKey, string> = {
"integrations.tab.zcode": "ZCode",
"integrations.tab.prime": "Prime Agent",
"integrations.tab.aside": "Aside",
"integrations.aside.profilesTitle": "Aside-Profile",
"integrations.aside.profilesHint": "Wähle, welche Profile die ausgewählten Modelle erhalten. Das aktive Aside-Profil bleibt unverändert.",
"integrations.aside.all": "Alle Profile synchronisieren",
"integrations.aside.syncNow": "Jetzt synchronisieren",
"integrations.aside.applied": "Bei {count} von {total} Profilen angewendet",
"integrations.aside.current": "Aktuelles Profil",
"integrations.aside.profile": "Profil {id}",
"integrations.aside.toggle": "{name} synchronisieren",
"integrations.aside.details": "{name} verwalten",
"integrations.aside.back": "Alle Aside-Profile",
"integrations.aside.empty": "Öffne Aside und erstelle ein Profil, um es zu verbinden.",
"integrations.aside.partial": "Einige Profile benötigen Aufmerksamkeit. Deine Synchronisierungsauswahl ist gespeichert. Prüfe den Status der einzelnen Profile.",
"integrations.aside.pending": "Synchronisierungsauswahl gespeichert; Dateiaktualisierung steht aus.",
"integrations.aside.retry": "Für {name} erneut versuchen",
"integrations.aside.loadError": "Aside-Profile konnten nicht geladen werden. Versuche es erneut, um ihren Status zu prüfen.",
"integrations.codex.title": "Codex CLI",
"integrations.codex.body": "Die Codex-Anbindung wird vom Proxy-Dienst verwaltet. Beim Start von opencodex wird sie angewendet; beim Stoppen des Dienstes wird das native Routing wiederhergestellt.",
"integrations.codex.openService": "Dienststeuerung öffnen",
Expand Down Expand Up @@ -1182,6 +1198,7 @@ export const de: Record<TKey, string> = {
"integrations.bulk.success": "Angewendete Client-Integrationen wurden deaktiviert.",
"integrations.retention.degraded": "Die Sicherungsbereinigung ist im Rückstand; ältere Sicherungen könnten noch auf dem Datenträger liegen.",
"integrations.error.residual": "Die Datei könnte sich in einem Zwischenzustand befinden: {message} Stellen Sie sie aus {path} wieder her.",
"integrations.error.residualNoSnapshot": "{message} Die automatische Wiederherstellung wurde nicht abgeschlossen. Prüfe die Client-Konfiguration, bevor du es erneut versuchst.",
"integrations.error.recover": "{message} Eine Sicherung liegt unter {path}.",
"integrations.kind.apply": "Angewendet",
"integrations.kind.disable": "Deaktiviert",
Expand All @@ -1206,7 +1223,7 @@ export const de: Record<TKey, string> = {
"integrations.semantics.mcode": "Verwaltet nur custom_provider.opencodex. Standardmodell und MiniMax-Anmeldung bleiben unverändert.",
"integrations.semantics.zcode": "Verwaltet nur provider.opencodex in ~/.zcode/v2/config.json. Z.ai-Anmeldung und andere Provider bleiben unverändert. ZCode nach Änderungen neu starten.",
"integrations.semantics.prime": "Verwaltet nur providers.opencodex in der models.json von Prime Agent — ~/.prime/agent, sofern PRIME_AGENT_CODING_AGENT_DIR sie nicht umleitet. Andere Provider und Modell-Overrides bleiben unverändert. Gilt für neue Sitzungen.",
"integrations.semantics.aside": "Verwaltet nur providers.opencodex in der models.json von Aside für das angemeldete Konto (~/.aside/u/<Konto>). Andere Provider bleiben unverändert. Aside überschreibt diese Datei im laufenden Betrieb, daher nach dem Anwenden vollständig beenden und neu öffnen.",
"integrations.semantics.aside": "Verwaltet nur providers.opencodex in der ~/.aside/u/<id>/models.json dieses Profils. Andere Provider bleiben unverändert. Beende Aside nach dem Anwenden vollständig und öffne es erneut.",
"codexAuth.mainAccount": "Hauptkonto",
"codexAuth.logLabel": "Log-Kennung",
"codexAuth.codexApp": "Codex App",
Expand Down
Loading
Loading