diff --git a/AGENTS.md b/AGENTS.md index f71dc2dd0f3..d5259be3a24 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -113,10 +113,10 @@ different pointer, so every branch move and promotion dragged a diff. Reference *clones* are gitignored: they are third-party source carrying their own licenses and have no business in this repository's history. -Nothing in the build, typecheck, or test path reads from `devlog/`, so a -contributor who ignores it entirely still passes every gate. `privacy:scan` does -read it — that is deliberate, and it is what makes a public devlog safe rather -than merely visible. +Build and typecheck do not read `devlog/`. The test-layout membership oracle reads +`devlog/_fin/260905_test_modularization_and_windows/001_test_inventory.md`; sparse +checkouts running that test must materialize this tracked input. `privacy:scan` +also reads `devlog/` to keep public development notes safe to publish. Two mechanical guards in `tests/ci-workflows/repo-hygiene.test.ts` back this up: no `160000` gitlink may be tracked anywhere, and neither the vendored reference clones nor diff --git a/bin/ocx.mjs b/bin/ocx.mjs index b33afd64095..72eff87dfcf 100755 --- a/bin/ocx.mjs +++ b/bin/ocx.mjs @@ -12,6 +12,7 @@ import { guardedRun, guardedSpawn, guardedSpawnSync, guardedExit, probeProcessAl import { probeSpawn, probeSpawnSync } from "../src/update/mutation-guard.mjs"; import { createSupervisionLatch, inspectDesktopSupervision } from "../src/service/desktop-supervision.mjs"; import { planDesktopCliHandoff, runDesktopCliHandoff } from "../src/lib/desktop-cli-handoff.mjs"; +import { forwardLauncherSignals } from "../src/lib/launcher-signal-forwarding.mjs"; import { acquireSyncGuard } from "../src/update/sync-update-mutex.mjs"; import { desktopCliRecordPath } from "../src/lib/desktop-cli-record.mjs"; import { STOP_HISTORY_INCOMPLETE_EXIT_CODE } from "../src/update/stop-contract.mjs"; @@ -1608,6 +1609,8 @@ function createNodeLaunchContext() { return { launchProof, launchContext, inheritedEnv }; } +// Forwarders precede the spawn so no signal can kill this launcher by default action while the child runs. +const forwarding = forwardLauncherSignals(); const child = probeSpawn(bun, [cliPath, `${NODE_LAUNCH_PROOF_PREFIX}${launchProof}`, ...process.argv.slice(2).filter(arg => !arg.startsWith(NODE_LAUNCH_PROOF_PREFIX))], { stdio: "inherit", // A headless Windows parent (Task Scheduler, dashboard restart, shortcut) has no @@ -1622,6 +1625,7 @@ const child = probeSpawn(bun, [cliPath, `${NODE_LAUNCH_PROOF_PREFIX}${launchProo [BUN_RUNTIME_PATH_ENV]: bunRuntime.path, }, }); +forwarding.attach(child); let delegateError = false; if (child.pid) { try { addSyncDelegate(child.pid); } @@ -1632,31 +1636,14 @@ if (child.pid) { } } -// Windows has no real POSIX signals (no SIGHUP); forwarding is best-effort there. -const FORWARDED = process.platform === "win32" ? ["SIGINT", "SIGTERM"] : ["SIGINT", "SIGTERM", "SIGHUP"]; -const handlers = FORWARDED.map(sig => { - const handler = () => { - try { - child.kill(sig); - } catch { - /* child already exited */ - } - }; - process.on(sig, handler); - return [sig, handler]; -}); -const clearHandlers = () => { - for (const [sig, handler] of handlers) process.removeListener(sig, handler); -}; - child.on("error", err => { - clearHandlers(); + forwarding.clear(); console.error(`opencodex: failed to launch Bun runtime: ${err.message}`); process.exit(1); }); child.on("exit", (code, signal) => { - clearHandlers(); + forwarding.clear(); releaseSyncLease(); if (delegateError) process.exit(1); // Mirror the child's terminating signal/exit code so this launcher's status matches. diff --git a/desktop/src-tauri/Cargo.lock b/desktop/src-tauri/Cargo.lock index 960e6146bcf..ddc9ff16b55 100644 --- a/desktop/src-tauri/Cargo.lock +++ b/desktop/src-tauri/Cargo.lock @@ -2633,7 +2633,7 @@ dependencies = [ [[package]] name = "opencodex-desktop" -version = "2.98.0" +version = "2.99.0" dependencies = [ "base64 0.22.1", "dbus", diff --git a/desktop/src-tauri/Cargo.toml b/desktop/src-tauri/Cargo.toml index 9707db5345a..201998c2a13 100644 --- a/desktop/src-tauri/Cargo.toml +++ b/desktop/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "opencodex-desktop" -version = "2.98.0" +version = "2.99.0" description = "OpenCodex desktop shell" authors = ["OpenCodex contributors"] license = "MIT" diff --git a/desktop/src-tauri/src/lib.rs b/desktop/src-tauri/src/lib.rs index ed610844745..fdc10147b1d 100644 --- a/desktop/src-tauri/src/lib.rs +++ b/desktop/src-tauri/src/lib.rs @@ -56,6 +56,8 @@ mod runtime_stop; mod sidecar; mod startup; mod supervisor; +#[cfg_attr(not(target_os = "macos"), allow(dead_code))] +mod traffic_lights; mod tray; mod tray_availability; mod updater; @@ -405,14 +407,16 @@ pub fn run() { // zoom the window through `plugin:window` commands granted by // `capabilities/dashboard-titlebar.json`. Windows and Linux keep the native title // bar — the shell ships no min/max/close widgets of its own — while the sidebar-top - // layout applies unchanged. + // layout applies unchanged. The vertical inset depends on the macOS release, whose + // button metrics changed in 26 (`traffic_lights.rs`). #[cfg(target_os = "macos")] let builder = builder .title_bar_style(tauri::TitleBarStyle::Overlay) .hidden_title(true) .min_inner_size(360.0, 320.0) .traffic_light_position(tauri::Position::Logical(tauri::LogicalPosition::new( - 18.0, 22.0, + traffic_lights::X, + traffic_lights::y_for(traffic_lights::macos_major()), ))); let window = builder.build()?; window::configure(&window); diff --git a/desktop/src-tauri/src/native_tray_accounts.rs b/desktop/src-tauri/src/native_tray_accounts.rs index 54309ce6749..8d2394bcf6f 100644 --- a/desktop/src-tauri/src/native_tray_accounts.rs +++ b/desktop/src-tauri/src/native_tray_accounts.rs @@ -151,7 +151,7 @@ fn windows(quota: &Value, plan: &str) -> Vec { } pub fn provider(source: &Source, body: Option<&Value>, unavailable: bool) -> Value { - let parsed = body.and_then(parse_accounts); + let parsed = body.and_then(|body| parse_accounts(body, source.switch)); let malformed = body.is_some() && parsed.is_none(); let accounts = parsed.unwrap_or_default(); let mut row = json!({"id":source.name,"label":source.label, @@ -165,7 +165,7 @@ pub fn provider(source: &Source, body: Option<&Value>, unavailable: bool) -> Val row } -fn parse_accounts(body: &Value) -> Option> { +fn parse_accounts(body: &Value, kind: Option) -> Option> { let rows = body .get("accounts") .or_else(|| body.get("keys"))? @@ -175,9 +175,16 @@ fn parse_accounts(body: &Value) -> Option> { .find_map(|key| body[key].as_str()); rows.iter().enumerate().map(|(index,row)| { let id = row["id"].as_str()?; - let email = row["email"].as_str().map(mask_email); - let label = [row["alias"].as_str(),row["label"].as_str(),email.as_deref(),row["logLabel"].as_str(),Some(id)] - .into_iter().flatten().find(|v| !v.is_empty()).unwrap_or(id); + let oauth = kind == Some(AccountSwitchKind::OAuth); + let email = if oauth { None } else { row["email"].as_str().map(mask_email) }; + let label = if oauth { + [row["alias"].as_str(), row["maskedLabel"].as_str()] + .into_iter().flatten().map(str::trim).find(|v| !v.is_empty()) + .map(str::to_owned).unwrap_or_else(|| format!("Account {}", index + 1)) + } else { + [row["alias"].as_str(),row["label"].as_str(),email.as_deref(),row["logLabel"].as_str(),Some(id)] + .into_iter().flatten().find(|v| !v.is_empty()).unwrap_or(id).to_owned() + }; let unavailable = row["quotaUnavailable"].as_bool()==Some(true) || text(row,"quotaMode")=="unsupported" || !row["quota"].is_object(); let is_active = active.map_or(row["active"].as_bool()==Some(true),|selected|selected==id); @@ -230,6 +237,66 @@ fn exhausted(quota: &Value, plan: &str) -> bool { #[cfg(test)] mod tests { use super::*; + fn parse_accounts(body: &Value) -> Option> { + super::parse_accounts(body, None) + } + + fn oauth_accounts(body: Value) -> Vec { + let source = Source { + name: "example".into(), + label: "Example".into(), + path: None, + switch: Some(AccountSwitchKind::OAuth), + }; + provider(&source, Some(&body), false)["accounts"] + .as_array() + .unwrap() + .clone() + } + + #[test] + fn oauth_alias_is_trimmed_and_does_not_expose_legacy_email() { + let rows = oauth_accounts(json!({"accounts":[{"id":"private-id","alias":" Work ", + "maskedLabel":"m•••@e•••.com","email":"person@example.com"}]})); + assert_eq!(rows[0]["label"], "Work"); + assert!(rows[0]["email"].is_null()); + assert_eq!(rows[0]["accountId"], "private-id"); + assert_eq!(rows[0]["id"], "private-id:0"); + } + + #[test] + fn oauth_masked_label_follows_blank_alias() { + let rows = oauth_accounts(json!({"accounts":[{"id":"private-id","alias":" ", + "maskedLabel":" m•••@e•••.com ","label":"legacy","email":"person@example.com"}]})); + assert_eq!(rows[0]["label"], "m•••@e•••.com"); + assert!(rows[0]["email"].is_null()); + } + + #[test] + fn oauth_legacy_payload_uses_ordinal_instead_of_identifiers() { + let rows = oauth_accounts(json!({"accounts":[ + {"id":"private-id","email":"person@example.com","label":"legacy", "logLabel":"legacy-log"}, + {"id":"another-private-id","alias":" ","maskedLabel":" "}]})); + assert_eq!(rows[0]["label"], "Account 1"); + assert_eq!(rows[1]["label"], "Account 2"); + assert!(rows[0]["email"].is_null()); + assert!(rows[1]["email"].is_null()); + assert_eq!(rows[0]["accountId"], "private-id"); + assert_eq!(rows[1]["accountId"], "another-private-id"); + } + + #[test] + fn codex_and_api_key_labels_keep_the_existing_projection() { + let body = json!({"accounts":[{"id":"id","label":"Legacy label", + "maskedLabel":"ignored","email":"person@example.com"}]}); + for kind in [AccountSwitchKind::Codex, AccountSwitchKind::ApiKey] { + let rows = super::parse_accounts(&body, Some(kind)).unwrap(); + assert_eq!(rows[0]["label"], "Legacy label"); + assert_eq!(rows[0]["email"], "p•••@e•••.com"); + assert_eq!(rows[0]["accountId"], "id"); + } + } + #[test] fn source_routes_are_encoded_and_never_forward_credentials() { let rows = sources(&json!({"providers":{ diff --git a/desktop/src-tauri/src/traffic_lights.rs b/desktop/src-tauri/src/traffic_lights.rs new file mode 100644 index 00000000000..f578711606e --- /dev/null +++ b/desktop/src-tauri/src/traffic_lights.rs @@ -0,0 +1,126 @@ +//! Where the macOS traffic lights sit over the overlay title bar. +//! +//! The dashboard's sidebar strip (`gui/src/components/app-titlebar.css`) is a 40pt row with an +//! 80pt lead inset, and the native lights have to sit centered on that row. tao places them by +//! resizing the title-bar container to `close button height + y` and moving each button to +//! `x + i * spacing`; the button keeps its own offset inside the container. So the same `y` +//! lands differently when AppKit's button metrics change, and they did change in macOS 26: +//! +//! | design | close button | spacing | y=22 centers at | y centering on 20 | +//! |-----------------|--------------|---------|-----------------|-------------------| +//! | macOS 26 Tahoe | 14 x 14 | 23 | 20 | 22 | +//! | macOS 15 and up | 14 x 16 | 20 | 24 (4pt low) | 18 | +//! +//! Both rows were measured with an AppKit probe that replays tao 0.37.2's +//! `inset_traffic_lights` on an overlay window: once with the Tahoe design and once with the +//! pre-Tahoe design forced through `UIDesignRequiresCompatibility` (#8280). Horizontally both +//! designs start at x and end inside the strip's 80pt inset (Tahoe at 78pt, pre-Tahoe at 72pt), +//! so only `y` depends on the OS. + +/// Lead inset of the close button, in points. `--tl-inset` in app-titlebar.css is this plus +/// 52pt of lights and 10pt of air. +pub const X: f64 = 18.0; +/// macOS 26 (Tahoe) and later: 14pt-tall buttons centered on the 40pt row. +pub const Y_TAHOE: f64 = 22.0; +/// macOS 15 (Sequoia) and earlier: 16pt-tall buttons need 4pt less to land on the same line. +pub const Y_PRE_TAHOE: f64 = 18.0; +/// The first release with the taller title bar metrics. Apple went from 15 straight to 26. +const TAHOE_MAJOR: u32 = 26; + +/// The `y` for a given macOS major version. An unknown version keeps the Tahoe value, which is +/// what every build shipped before this distinction existed. +pub fn y_for(macos_major: Option) -> f64 { + match macos_major { + Some(major) if major < TAHOE_MAJOR => Y_PRE_TAHOE, + _ => Y_TAHOE, + } +} + +/// The major component of a product version such as `15.7.1` or `26.0`. +fn parse_major(version: &str) -> Option { + version.trim().split('.').next()?.parse().ok() +} + +/// The running macOS major version, from `kern.osproductversion`. Unlike the +/// `SystemVersion.plist` reads behind some version APIs, this sysctl is not redirected by +/// `SYSTEM_VERSION_COMPAT`, so it reports the real release. +#[cfg(target_os = "macos")] +pub fn macos_major() -> Option { + use std::ffi::{c_char, c_int, c_void, CStr}; + extern "C" { + fn sysctlbyname( + name: *const c_char, + oldp: *mut c_void, + oldlenp: *mut usize, + newp: *mut c_void, + newlen: usize, + ) -> c_int; + } + let mut buffer = [0u8; 32]; + let mut len = buffer.len(); + // SAFETY: the name is NUL-terminated, `buffer` is writable for `len` bytes, and no new + // value is set. + let status = unsafe { + sysctlbyname( + c"kern.osproductversion".as_ptr(), + buffer.as_mut_ptr().cast(), + &mut len, + std::ptr::null_mut(), + 0, + ) + }; + if status != 0 { + return None; + } + let version = CStr::from_bytes_until_nul(&buffer[..len.min(buffer.len())]).ok()?; + parse_major(version.to_str().ok()?) +} + +#[cfg(test)] +mod tests { + use super::{parse_major, y_for, X, Y_PRE_TAHOE, Y_TAHOE}; + + #[test] + fn product_versions_parse_to_their_major() { + assert_eq!(parse_major("26.6.2"), Some(26)); + assert_eq!(parse_major("15.7.1"), Some(15)); + assert_eq!(parse_major("14.0"), Some(14)); + assert_eq!(parse_major("10.16"), Some(10)); + assert_eq!(parse_major(" 13 "), Some(13)); + assert_eq!(parse_major(""), None); + assert_eq!(parse_major("abc"), None); + } + + #[test] + fn sequoia_and_earlier_lift_the_lights_onto_the_strip() { + for major in [10, 11, 12, 13, 14, 15] { + assert_eq!(y_for(Some(major)), Y_PRE_TAHOE, "macOS {major}"); + } + for major in [26, 27, 30] { + assert_eq!(y_for(Some(major)), Y_TAHOE, "macOS {major}"); + } + assert_eq!(y_for(None), Y_TAHOE); + } + + /// The measured geometry behind the constants: tao sets the container to + /// `button_height + y`, AppKit keeps the button's default offset from the container's bottom + /// edge, and the light should be centered on the 40pt strip row. + #[test] + fn both_designs_center_on_the_strip_row() { + // Center of the light, measured from the window's top edge. + let center = |height: f64, bottom: f64, y: f64| (height + y) - bottom - height / 2.0; + // Tahoe: a 14pt button whose default frame origin is (9, 9). + assert_eq!(center(14.0, 9.0, Y_TAHOE), 20.0); + // Pre-Tahoe: a 16pt button whose default frame origin is (7, 6). + assert_eq!(center(16.0, 6.0, Y_PRE_TAHOE), 20.0); + assert_eq!(center(16.0, 6.0, Y_TAHOE), 24.0); + assert_eq!(X, 18.0); + } + + #[cfg(target_os = "macos")] + #[test] + fn the_running_macos_reports_a_major_version() { + let major = super::macos_major().expect("kern.osproductversion"); + assert!(major >= 10, "{major}"); + } +} diff --git a/desktop/src-tauri/tauri.conf.json b/desktop/src-tauri/tauri.conf.json index 544a26fe31b..892d5c37df0 100644 --- a/desktop/src-tauri/tauri.conf.json +++ b/desktop/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "https://schema.tauri.app/config/2", "productName": "OpenCodex", - "version": "2.98.0", + "version": "2.99.0", "identifier": "com.opencodex.desktop", "build": { "frontendDist": "../ui", diff --git a/devlog/_plan/261107_startup_safety_and_account_labels/000_plan.md b/devlog/_plan/261107_startup_safety_and_account_labels/000_plan.md new file mode 100644 index 00000000000..3d0501517df --- /dev/null +++ b/devlog/_plan/261107_startup_safety_and_account_labels/000_plan.md @@ -0,0 +1,79 @@ +# Startup safety and account labels + +Desktop users need a quiet, truthful startup verdict when login registration cannot +be checked, and OAuth pool users need recognizable, privacy-preserving account +names. Preserve conservative restart-protection credit while distinguishing an +unknown observation from a verified disabled setting. Render user aliases first, +then a server-projected masked identity, then a localized ordinal. + +- Loop: satisfy-spec, triggered by the owner request; goal is both changes merged to `dev`. +- Scope: Desktop login evidence and dashboard presentation; OAuth roster and account + label surfaces; their tests, all GUI locales, public and ownership documentation. +- Out: shared inline-label background style; real user installation, services and + login items; root full suite; new dependencies; native stack registration. +- Evidence: failing-first regressions, focused suites, typecheck, GUI checks, + file-size ratchet, i18n, React Doctor, privacy and structure checks, isolated + browser screenshots and exact-head CI plus a fresh independent review. +- Stop: after the authorized PR is merged and the squash SHA reported; blocked + execution and unmet gates are reported without declaring completion. +- Memory: this unit and its numbered phase documents; session goalplan and receipts. +- Escalate only for a necessary unavailable capability or an unauthorized action. + No token or time bounds stated; host limits apply. Keep host load moderate. + +## Dependency order + +1. Docs-only roadmap and independent audit (this cycle). +2. [010 implementation](010_implementation.md): evidence contract and safe + account projection, then their presentation and tests in one coherent change. +3. [020 integration](020_integration.md): exact-head review, CI and integration. + Use one ordinary PR unless the actual diff proves too large to review safely; + phase ordering by itself is not a reason to stack. + +## Source evidence and architect consultation + +Baseline `41f316f40e` matched freshly fetched `origin/dev`. The service diagnostic +collapses failed and disabled probes in `src/service/desktop-startup.ts`, while +`gui/src/pages/startup-sections.tsx` repeats one recovery key three times. The +OAuth roster returns config-dependent `email` from `src/oauth/index.ts` and +`src/server/management/oauth-account-routes.ts`; the GUI requests ordinal-only for +Anthropic in `gui/src/components/provider-workspace/ProviderAuthPanel.tsx`. +The macOS tray and service share enabled-credit fixtures as documented in +`structure/desktop-start-at-login.md`. + +Architect handle `node:delegated-task:command%3Amcp%3A2f716394-9556-49bf-a7d0-57ef9242bf2e%3Adelegate-task%3Astartup-architect-proposal-1`: +ARCH-01 additive tri-state accepted, with disabled only on stable evidence for +the actual app; absence without independently established ownership stays unknown. +ARCH-02 conservative backend credit and neutral GUI presentation accepted for +both live supervised and owned proxy; independently known risks remain visible. +ARCH-03 one explanation accepted. ARCH-04 masked email projection accepted; +no opaque ID fallback, malformed identities use ordinal. Aliases remain first. +Reflection round 1 was MISALIGNED: stable negative checks, stale/cached presentation, +system-disabled guidance and all OAuth fallback consumers needed detail. These four +findings were accepted and incorporated in `010_implementation.md`. The same +architect's reflection round 2 returned ALIGNED for ARCH-01 through ARCH-04, +with no remaining material gaps. Independent audit round 1 returned FAIL: omitted +Classic/page-level presentation, an explicit fixture-backed browser harness, security +review, and `ci:full` were accepted and added to 010/020. The same independent +reviewer's round 2 returned PASS: all four findings closed, no new high or critical +findings, and all 35 named paths resolved. This is plan-level evidence only. + +## Baseline checks + +After `bun install --frozen-lockfile` in root and `gui/` (both exit 0), +`bun test tests/gui/startup-health-ui.test.ts tests/gui/provider-workspace-auth.test.ts tests/service/service-desktop-login-registration.test.ts` +exited 0, 100 pass; it directly reads the named tests and their imports. +`bun run structure:check` exited 0 and reads `structure/` through +`scripts/structure-ssot.ts`. `cd gui && bun run lint:i18n` exited 0 and +reads its configured `gui/src/pages`, `gui/src/components` and other named GUI files; +it is not proof of translation completeness by itself. + +## Roadmap cycle conclusion + +The independent re-audit passed after four findings were folded in. A fresh bound +document check found three non-empty documents, required contract/integration gates, +and clean whitespace. Next: revalidate 010 against current source and begin its +failing-first implementation cycle. No product code was changed in the roadmap cycle. +The initial assumption that an existing roster label was always safe did not hold: +the email field follows a configurable masking policy, so the plan now requires a +dedicated masked projection. A test with masking disabled would refute that direction +if the projected label exposed the original identity. diff --git a/devlog/_plan/261107_startup_safety_and_account_labels/010_implementation.md b/devlog/_plan/261107_startup_safety_and_account_labels/010_implementation.md new file mode 100644 index 00000000000..644b50f02bd --- /dev/null +++ b/devlog/_plan/261107_startup_safety_and_account_labels/010_implementation.md @@ -0,0 +1,247 @@ +# Implementation: evidence, safe labels and presentation + +Depends on the audited roadmap. Revalidate paths and current `origin/dev` at P. + +## Desktop state contract + +MODIFY `src/service/desktop-startup.ts`: retain `loginEnabled` and existing +fail-closed `viable` semantics; add additive `loginState: enabled | disabled | +unknown` to `DesktopStartupDiagnostic`. Creation: verified macOS registration +and launchctl evidence for the same app yields enabled; stable, unambiguous +disabled evidence for that app yields disabled; every mismatch, failure, +unsupported platform, race and older/default snapshot yields unknown. Never infer +disabled solely from `loginEnabled === false`. Establish the running app through +independent ownership/supervision evidence before attributing a negative registration. +Linux negatives and Windows are unknown until independently proven. Preserve +the existing protection credit and shared Rust/TS enabled-registration parity. +Map the existing two-capture Linux verified positive to enabled; never make the +presentation contradict its existing protection credit. Map both owned and unowned +supervised Linux positives to enabled; `tests/service/service-desktop-startup-linux.test.ts` +must cover the supervised path through health API and both UI presentations. The owned macOS path +currently derives executable identity from registration, so a missing registration +must use independently verified supervision plus durable ownership or stay unknown; +an install ID alone does not establish the executable. + +For a disabled override require a uniquely parsed `disabled`/`true` override on +a valid registration for the independently identified app. Compare registration, +executable identities and override output across two captures, and revalidate +ownership revision and live parent/child process chain across the observation. +For absence require explicit `ENOENT`, independently established app ownership, +two absent observations and revalidated ownership and process identity. A dangling +link, permission error, another error, replacement, other copy or changed input is +unknown. If the required evidence cannot be obtained, absence stays unknown. Test +override changes, absent-to-created registration, ownership and parent changes, +and malformed/ambiguous `print-disabled` output. The tray's `Off` alone is not a +stable negative observation. + +Field chain: creation in `src/service/desktop-startup.ts`; serialization via +`src/codex/autostart-health.ts`, its startup-health subprocess/cache and management +response; deserialization in `gui/src/pages/startup-shared.ts` and dashboard poll; +consumers in `gui/src/startup-health-ui.ts`, `gui/src/pages/startup-sections.tsx`, +`gui/src/pages/dashboard-core-poll.ts`, `gui/src/pages/use-dashboard-data.ts`, +`gui/src/pages/dashboard-overview-head.tsx`, `gui/src/pages/Startup.tsx`, +`gui/classic/src/pages/Startup.tsx`, `gui/classic/src/pages/startup-sections.tsx`, +`gui/classic/src/pages/dashboard-overview-head.tsx`, `gui/src/pages/Home.tsx`, +`gui/src/pages/SetupHub.tsx` and settings-seed types. Audit and test Home/SetupHub +checklist semantics against the evidence state. Search all +`loginEnabled`, `DesktopStartupDiagnostic`, `startupHealth` and status consumers +before coding. Missing/invalid field in an older response means unknown. + +MODIFY GUI decision/presentation files above: a known desktop-owned or supervised +local proxy with unknown, stale, unavailable or missing login evidence presents a +neutral, accessible unverified state, not a warning or green protected verdict. +Verified disabled plus no independently verified protection displays action required; +verified enabled and viable displays protected. Service protection wins; custom +routing and independent real risks retain truthful handling. A total fetch failure +without reliable Desktop context remains an unavailable/error state, not an assumed +Desktop. Keep backend reboot safety conservative. Carry this classification through +settings seeds, dedicated polls, session cache and revisits; invalidate or safely +reinterpret old status-only cached entries. Exercise cold ownership-only fallback, +stale prior enabled/off, failed refresh, older payload, seed/poll ordering and an +independent risk. Preserve fast stale refresh and do not suppress unrelated risks. +Update parent-level stale/failure banners as well as section heroes in both editions; +Classic's dashboard must explicitly handle the new neutral presentation, never fall +through to the green/native state. Render tests for both editions must cover stale, +failed refresh and old cache, plus the other state cases above. +Use a single concise hero explanation and a clear menu action; cards give short +status and recovery contains commands, not repeated prose. An existing desktop +command may be used only if its availability and ownership safeguards are verified; +otherwise give the menu instruction without mutating login items. Preserve keyboard, +semantic status and live-region accessibility. Do not change shared label styles. +For verified-off guidance direct the user through the OpenCodex menu without claiming +that toggling the item resolves a macOS system-disabled override: the tray opens Login +Items settings for that case. Update the unconditional backend recommendation too. +Also update the separate CLI summary in `src/codex/autostart-health.ts` so it does +not claim a menu toggle can clear a system override. + +## OAuth identity contract + +MODIFY `src/server/management/oauth-account-routes.ts` and, if appropriate, a +small existing privacy helper: add a named masked label from the stored identity, +independent of `privacy.maskEmails`. Validate email shape before masking because +`src/lib/privacy.ts` returns malformed non-email input unchanged; absent, malformed +or ambiguous identities produce no label. Keep current email configuration behavior +for other consumers. Never log raw account identifiers or expose raw email through +the new label. Field chain: stored credential -> roster projection -> JSON response +(including quota projection) -> `OAuthAccount` in `gui/src/hooks/useProviderAccountPools.ts` +-> `gui/src/provider-workspace/auth.ts` -> `ProviderAuthPanel.tsx`, dialogs and +confirmation/notification consumers. Check secondary text for raw-email leaks. +Replace `ordinalOnly` with alias, the new masked label only, then localized ordinal +for all OAuth pools, including both Anthropic instances; an old server without the +field must not fall back to the config-dependent email. Update both `OAuthAccount` +and `OAuthAccountRow` in `gui/src/components/provider-workspace/types.ts`, row +headings, accessible names, threshold controls, coupon/grant dialogs and switch, +pause and remove confirmations and notifications. Remove raw `account.email` from +secondary identification and replace the no-roster `oauth.email` status fallback +with a safe label or generic localized status. Test these surfaces with masking off, +missing field, malformed identity, alias and both Anthropic instances. Never use +opaque IDs as labels. +Include `gui/src/pages/tray-data.ts`, both `gui/src/pages/Tray.tsx` and +`gui/classic/src/pages/Tray.tsx`, their accessible switch names and +`gui/tests/tray-data.test.ts`: OAuth rows use alias -> safe masked label -> localized +ordinal while preserving existing Codex and API-key behavior. Pass source kind and +translation context where necessary instead of treating all roster shapes alike. +OAuth secondary identification may use only the server-projected safe label or be +omitted; never derive it from the configurable email, including for older servers. +Render both editions' trays with alias, safe label and a missing safe field plus a +populated legacy email; verify secondary text and accessible switch names. Preserve +Codex and API-key secondary behavior. + +## Regression and documentation map + +MODIFY `tests/service/service-desktop-login-registration.test.ts`, shared login +fixtures as necessary, `tests/service/service-desktop-startup.test.ts`, +`tests/service/service-desktop-startup-health.test.ts`, and startup API/cache +tests (`tests/service/autostart-health.test.ts`, `tests/config/settings-startup-health-seam.test.ts`, +`tests/server/startup-health-packaged-probe.test.ts`) for enabled, verified off, missing/invalid evidence, races, another copy, +stale state and service protection. Preserve Rust fixture parity and run its tests +if the shared fixture or Rust contract changes. MODIFY `tests/gui/startup-health-ui.test.ts` +and dashboard render/poll tests for unknown/off/enabled and old payload, including +single explanation and Home/SetupHub checklist cases. MODIFY `tests/gui/provider-workspace-auth.test.ts` and OAuth +management tests (`tests/oauth/oauth-accounts-api.test.ts`, +`tests/server/account-pool-management-api.test.ts`, `tests/server/provider-account-quota-routes.test.ts`) +for both Anthropic instances, aliases, masked vs unmasked privacy +configuration, malformed identities, quota response and accessible labels. Write +the behavior regressions first, run them and record red before implementing. + +MODIFY every GUI locale for new/changed copy; check complete key parity, not only +lint. MODIFY `docs-site/src/content/docs/guides/desktop-app.md` and relevant +translated descriptions to avoid contradiction; MODIFY the owners identified in +`structure/INDEX.md`, especially `structure/desktop-start-at-login.md`, +`structure/ops/service-and-sidecars.md`, and `structure/gui-and-management-api.md`. +Do not add a new test file without both layout registration and expected fixture. + +Verification: run named focused tests, `bun run test:changed`, GUI tests/lint/build, root typecheck, +`bun run structure:check`, privacy scan, file-size ratchet and GUI i18n plus +React Doctor. Baseline commands and their target coverage are in `000_plan.md`; +revalidate additional commands against package scripts and tests before execution. +No full root suite: owner explicitly requires moderate host load. Run the GUI with +temporary isolated `OPENCODEX_HOME`, `CODEX_HOME`, Claude homes, a non-default +port and synthetic accounts; capture before/after using GOAT browser, upload only +through the `pr-assets` branch and link immutable commit SHAs. Never touch the real +app, service, login items or credentials. Check each conditional path through its +named tests and visually inspect both startup and account surfaces. + +Concrete focused commands (in repository root): `bun test tests/service/service-desktop-login-registration.test.ts tests/service/service-desktop-startup.test.ts tests/service/service-desktop-startup-linux.test.ts tests/service/service-desktop-startup-health.test.ts tests/service/autostart-health.test.ts tests/config/settings-startup-health-seam.test.ts tests/server/startup-health-packaged-probe.test.ts`; `bun test tests/oauth/oauth-accounts-api.test.ts tests/server/account-pool-management-api.test.ts tests/server/provider-account-quota-routes.test.ts tests/gui/provider-workspace-auth.test.ts tests/gui/startup-health-ui.test.ts`; and from `gui/`, `bun test tests/startup-minimal.test.tsx tests/startup-revisit-cache.test.tsx tests/dashboard-contracts.test.ts tests/tray-data.test.ts` plus affected account render tests. The named tests import their targets directly; the baseline startup/UI and OAuth commands ran successfully as recorded in 000, while the added suite commands must be run and recorded before relying on them. Add main/Classic rendered regressions through the real Vite Classic overlay, not raw Bun imports: `vite.classic.config.ts` resolves Classic files to overrides or shared `src` dependencies. Exercise the built `/classic/` through the fixture-backed proxy in GOAT alongside the main edition; assert stale, failed refresh and old cache there, and record observable DOM and screenshots. `bun run build` in `gui/` builds both editions using the overlay. + +Baseline command `bun test tests/config/settings-startup-health-seam.test.ts tests/server/startup-health-packaged-probe.test.ts tests/server/account-pool-management-api.test.ts tests/server/provider-account-quota-routes.test.ts tests/service/service-desktop-startup-linux.test.ts` exited 0 with 110 passing tests; these files directly import the named backend modules. This validates baseline targets, not the future change. + +Isolation must account for `src/config/owner-registry.ts`: a custom OpenCodex +home alone still writes its locator under the default home. Use the existing test +guard and a temporary `OCX_OWNER_REGISTRY_DIR`, and verify all client and service +paths are isolated before starting any proxy. macOS `homedir()` may still resolve +the real account; do not rely on a HOME rewrite alone. A guard refusal is a test +setup failure, not permission to use the real installation. + +Use a fixture-backed test proxy rather than a real desktop process: the existing +management reader seam in `src/server/management/context.ts` injects controlled +startup-health data, and the settings seed and dedicated startup endpoint must both +return the same chosen enabled, disabled or unknown fixture. Seed synthetic OAuth +accounts through test-owned state; block external quota/network probes. Confirm the +real GUI is served by that isolated proxy and inspect the relevant two pages in GOAT. + +## Implementation-cycle planning + +Prior D: roadmap audit passed; the original assumption that the configured email +projection was always masked was rejected. At `51db5cd279` the implementation +architect proposed P-ARCH-01 through P-ARCH-06: accept the additive conservative +contract, independently anchored negative evidence, shared neutral presentation, +state-specific guidance, dedicated masked roster projection and fixture harness. +The plan incorporates the existing Linux positive, Home/SetupHub checklist and CLI +summary. The same architect returned ALIGNED on all six decisions; its `test:changed` +verification omission was accepted and added above. Implementation audit round 1 +identified three medium findings: tray consumer chain, unowned Linux positive and +specific verification targets. All were accepted and added above. Same-architect +reflection initially found missing secondary tray identity coverage; that was added, +and its targeted recheck returned ALIGNED. Same-reviewer re-audit returned PASS, +with all three findings closed and no new high or critical gaps. + +## Implementation evidence so far + +The new UI regressions first failed on the unknown Desktop "Action required" banner +and on an Anthropic alias rendered as an ordinal; the new API regression failed on +the absent masked label. Focused reruns passed after implementation. The GUI suite +ran 3,950 tests with zero failures before the final target-switch guard; that guard was +subsequently covered by `gui/tests/dashboard-resource-deadline.test.tsx` (2 pass). +The latest focused backend run had 296 pass, and the latest GUI slice had 55 pass. +Root typecheck, GUI build/lint/i18n, locale parity, file-size ratchet, privacy scan, +structure check and React Doctor changed-file scan passed. The full root suite was +not run as requested. `bun run test:changed --parallel=1` exceeded the runner's +900-second limit and exited 124; it did not complete, and one GUI classifier edit +occurred during the run. Do not describe it as passing. The focused files and +`ci:full` cover the pending verification; exact-head hosted results are still needed. + +The isolated browser reproduced the old warning and ordinal labels, then observed a +neutral unknown state and masked synthetic account names in both main and Classic. +The proxy ran with temporary homes, test guard, an in-memory provider keychain, +loopback-only sandbox networking, and denied `security` and `launchctl` execution; +quota requests failed under that deliberate network restriction. Before/after images +are stored at the `pr-assets` commit 90a1122d79edcf3ae74d8b3cccc15dd70d9d9f4f. +GOAT's credential expired after its server restarted, so enabled/disabled browser +scenarios and the required fresh independent implementation/security review remain +unverified. The fixture-based tests cover those state mappings, but do not replace +the requested review or the remaining browser observation. + +The implementation also verifies a consistently absent macOS login item for the +independently observed running Desktop, including owned and supervised cases. Its +failing-first regression initially returned unknown; after the fix, 159 desktop/login +registration tests passed. A reappearing item, dangling link or changed ownership +does not receive a disabled verdict. Typecheck, the file-size ratchet, privacy scan +and structure check passed after this addition. The draft PR remains subject to the +same pending review and exact-head CI gates. + +A further presentation regression showed that a verified enabled login item with +an independent viability problem was incorrectly treated as unverified. The focused +UI test failed first (expected `at-risk`, received `unverified`); after restricting +the neutral classification to unknown or stale evidence it passed, 20 tests total. +This does not change the conservative backend protection calculation. + +An interim independent implementation review requested follow-up for the native +tray's account projection, stale readings without Desktop context, process/owner +races, and checklist retries. The new stale and enabled-evidence race regressions +failed before their fixes and pass afterward. A later recheck found that an exception +during the final process read could still retain enabled credit; its regression also +failed first and passed after the fix. Native tray tests passed in an isolated Rust +harness (11 tests); the rendered Home/Setup checklist tests passed (5 tests), including +stale-to-protected and stale-to-disabled transitions. The native panel follows its +existing English UI convention for ordinal fallback. The required fresh GOAT review +on the final head remains pending. + +After those fixes, `cd gui && bun test tests` exited 0 with 3,956 tests and +`cd gui && bun run build` exited 0 for both editions. The focused startup and +checklist suites, root typecheck, GUI lint and i18n lint, privacy scan, structure +check, and file-size ratchet also passed. The root full suite remains unrun by +owner request, and the timed-out `test:changed` run is not passing evidence. + +The branch was combined with `origin/dev` at `35d5010f502a` after the dashboard +layout change landed. The sole documentation conflict was resolved with both +contracts preserved. On that combined tree the GUI suite passed 3,965 tests; +typecheck, GUI lint and i18n lint, structure check and file-size ratchet passed. +Final-head CI, refreshed browser observations and the fresh GOAT review are still +required before integration. + +Exact-head Ubuntu shard 5 on `91f86939c4` found three Windows fixture assertions +that omitted the additive `loginState: "unknown"` field. The Windows supervision +suite reproduced all three failures locally; after updating those expectations, +all 17 tests passed, including the conservative claim-only cases. The failed CI +run remains failed evidence and must be replaced by checks on the corrected head. diff --git a/devlog/_plan/261107_startup_safety_and_account_labels/020_integration.md b/devlog/_plan/261107_startup_safety_and_account_labels/020_integration.md new file mode 100644 index 00000000000..428d7c50eed --- /dev/null +++ b/devlog/_plan/261107_startup_safety_and_account_labels/020_integration.md @@ -0,0 +1,26 @@ +# Integration: exact-head review and merge + +Depends on implementation and its verification. Re-fetch `origin/dev` and inspect +the union for overlap, especially the separate GUI sweep branch; test the union if +behind or overlapping. Never modify its inline-label background rule. + +Use the repository PR template, target `dev`, document exact commands, results and +the full-suite resource exception, and include before/after screenshots via immutable +`pr-assets` commit links. Link the PR to this GOAT thread. The owner explicitly +authorizes push, PR creation and merge. Complete a fresh independent `task.create` +review with role `review` on a model available in `provider.list`; record findings +and fixes, and require PASS on the final head. Inspect and resolve all review threads. +The final fresh reviewer must explicitly review credential-derived identity projection +and masking/privacy boundaries as security review, and its explicit security result +must be recorded with the integration evidence per `MAINTAINERS.md`. + +Verify current PR head and require green exact-head `ci` aggregate, `enforce-target`, +`hygiene`, and all full Ubuntu shards on the final head; missing, pending, skipped, cancelled or +older-head results do not count. Include file-size ratchet, all locale parity, +React Doctor, union checks and local validation evidence. If checks/review are +pending, use GOAT PR watch and end the turn; do not poll. Merge only after every +gate holds, with a short maintainer-integration comment and +`gh pr merge --squash --admin --match-head-commit `. Verify the merge and +report PR URL and actual squash SHA via `origin.report`; unwatch at handback. +Apply `ci:full` before the implementation push as required for subprocess and +source-as-data coverage; verify the label and full-lane selection on the exact head. diff --git a/devlog/_plan/261107_usage_apps_logs/000_plan.md b/devlog/_plan/261107_usage_apps_logs/000_plan.md new file mode 100644 index 00000000000..c447fbd401f --- /dev/null +++ b/devlog/_plan/261107_usage_apps_logs/000_plan.md @@ -0,0 +1,44 @@ +# Usage apps and request log chips + +## Objective and scope + +Show all apps and all devices by default in the usage report; offer a classified app breakdown after model and provider; replace the log surface tabs with a classified app chip at the start of each request row. Use only the existing bounded client attribution. Preserve the route conversion badge, source/device authorization and explicit selections. No auth, credential, workflow, dependency, or CI policy changes are planned. Resource bounds: none stated; host limits apply. + +## Current evidence + +- `gui/src/pages/Usage.tsx` initializes the client to `codex` in component state and the device to `all`. No source preference storage was found in the usage component or picker. An existing session's explicit choice is held in state; there is no persisted default to migrate in this revision. +- `src/usage/client.ts` defines a closed set of client labels and normalizes invalid values. `src/usage/summary.ts` already applies source and device filtering before accumulation; its retained state is bounded and supports cloning, ranges, and projections. +- `gui/src/components/v2-usage/usage-breakdown.tsx` renders separate model and provider tables; an app breakdown needs server data at the same filtered scope, not a reconstruction from these totals. +- Baseline verifiers: `bun test tests/usage/usage-client-attribution.test.ts` (exit 0, 14 pass; directly imports aggregation); in `gui`, `bun test tests/usage-source-picker.test.tsx` (exit 0, 89 pass; mounts Usage and picker). + +## Planned change and acceptance + +1. Extend the summary accumulation with app aggregates based only on normalized client labels. Preserve date/range, device, model/provider filters, exact/row-unique accounting, clones, cache memory accounting and relay projections. Missing legacy attribution is unclassified, distinct from a valid `other` label. Add failing-first tests for mixed clients, missing/invalid clients, filtering, range and clones. +2. Default the report to `all`/`all`, retain live choice, render App as third breakdown after model and provider only for all apps, including localized unknown/unclassified labels. Cover defaults, selection and display with mounted GUI tests. +3. Remove request log app tabs and render a compact classified client chip first on each row, retain filtering controls and conversion badge. Test several clients, invalid/legacy data and row layout. +4. Synchronize applicable structure and all documented public locales; run focused tests, GUI tests/lint/build, typecheck, file-size and layout guards, Lab boundary, privacy and structure gates, and full suite where feasible. Validate browser against a synthetic isolated proxy with temporary homes; obtain before/after screenshots on pr-assets. Review diff independently and satisfy the owner's exact-head CI/review requirements before authorized merge. + +Consultation and source trace completed; design decisions and verification cases follow. No production changes in planning. + +## Architecture decisions (consultation) + +- APP-01/02 accepted: after source/window and combo projection, partition the app totals by a closed app roster; fold Codex variants into Codex while distinguishing missing legacy attribution and present unknown. Count logical rows once, preserving selected provider/model scope. +- APP-03 accepted: include app counters in accumulator clone and retained-memory accounting and replace them in `projectUsageSummary`; do not use 30-day source facets as the report data. +- APP-04 accepted: additive management field, bounded validation on GUI and explicit safe projection through `src/remote/hub-usage.ts`; test link/hub scope and older responses. Human CLI formatting is outside the requested GUI scope. +- APP-05 accepted: no prior persisted source preference exists in this revision. Change the initial component-state choice to all without inventing persistence; preserve an explicitly selected app during the mounted session and retain device restrictions. +- Log decision: remove surface radios and their hidden predicate in `gui/src/pages/logs-filter-bar.tsx` and `logs-filter.ts`; in `Logs.tsx` add a leading chip from a validated classified enum, with neutral unknown for absent or invalid values. Retain `ProtocolBadge` and the filter disclosure. Update their focused tests and translated `guides/web-dashboard.md` pages. +- The app breakdown presents request counts, token totals and cost using the same aggregate semantics as adjacent breakdowns. A third section follows models and providers; legacy and explicitly unknown labels remain distinct. The all-apps choice controls its visibility. + +## Consultation reflection and test matrix + +Architect reflected ALIGNED on APP-01–05. Accepted the verification and remote compatibility refinements. The user explicitly requires every legacy row without a client to display Unclassified, including rows with an existing surface; therefore this breakdown does not use the narrower `isLegacyCodexUsage` definition. Present invalid client values map to unknown. Tests must trigger custom-window endpoints, local-day/DST ranges, combo provider/model projections, both accumulation modes, clone independence and retained-byte growth. The hub field is optional for old responses, validated against a closed roster and bounded, and tests assert authenticated-key-only data and absence of key IDs. + +Field chain: `src/usage/summary.ts` creates app aggregates and serializes them in `UsageSummary`; management routes return them, `src/remote/hub-usage.ts` validates/projects the optional field, and `gui/src/components/v2-usage/usage-model.ts` and report response validation consume it before `usage-breakdown.tsx` renders it. Snapshot projection must replace aggregate data. No new enforcement mechanism is planned. + +## Audit disposition + +Independent audit: GO-WITH-FIXES (blockers=0). Accepted both medium findings: reset focuses the persistent conversation search after the radio controls are removed and keyboard/pointer tests verify it; echo rejection uses otherwise valid reports and changes only the source echo, with a non-all client for a mismatch. Added scenarios: nonempty report shows model → provider → app and hides app when a particular client is selected; a projected two-provider combo contributes one request and only retained token/cost; warm all/all and key caches, append another client, and compare with a fresh rebuild; hub tests cover omitted older fields, invalid/excessive labels, nested private fields, authenticated-key scope, and no key identity. + +## Owner correction + +The Logs chip uses only an SVG app mark, with no visible app name. Missing, invalid and other clients use a neutral question-mark SVG; the classified name remains available to assistive technology. The column stays narrow and the existing protocol badge remains legible. diff --git a/docs-site/src/content/docs/fr/guides/ollama-clients.md b/docs-site/src/content/docs/fr/guides/ollama-clients.md index a3e78b7a28f..58c132f1f2e 100644 --- a/docs-site/src/content/docs/fr/guides/ollama-clients.md +++ b/docs-site/src/content/docs/fr/guides/ollama-clients.md @@ -44,6 +44,11 @@ Tous les autres chemins `/api/*` restent dans l'API de gestion d'opencodex. - `options` : `temperature`, `top_p`, `num_predict` et `stop`. - `format` : uniquement `"json"`. +Chaque requête `/api/chat` accepte au maximum **1 024 appels d’outils au total** dans l’ensemble des messages +assistant, y compris ceux qui ont déjà un résultat. Au-delà, elle renvoie HTTP 400 avant d’atteindre +un fournisseur. Raccourcissez l’historique de la conversation et renvoyez-le, en conservant chaque +résultat d’outil avec l’appel correspondant dans le message assistant. + Tout le reste est refusé avec un HTTP 400 au lieu d'être ignoré : `think`, `keep_alive`, `suffix`, `raw`, `template`, `context`, les autres `options`, un `format` en schéma JSON et les URL d'images. Les points de terminaison Ollama de gestion des modèles et d'embeddings ne sont pas fournis. diff --git a/docs-site/src/content/docs/fr/guides/web-dashboard.md b/docs-site/src/content/docs/fr/guides/web-dashboard.md index 4a3f3e37036..8dad87f1b94 100644 --- a/docs-site/src/content/docs/fr/guides/web-dashboard.md +++ b/docs-site/src/content/docs/fr/guides/web-dashboard.md @@ -93,15 +93,23 @@ force jamais d'actualisation en amont. Les vues Utilisation, Tableau de bord, Fournisseurs, Catalogue des fournisseurs et Clés API signalent les enregistrements exclus, même sans résultat lisible. Les décomptes, les dates et les classements reposent uniquement sur les lignes lisibles. L’enregistrement de l’ordre des modèles par utilisation est refusé si l’historique est incomplet : choisissez un autre ordre ou réparez l’historique avant de réessayer. +### Utilisation par application + +La page Utilisation s’ouvre avec **Toutes les applications**, **Tous les appareils** et **30 jours** dans le périmètre d’accès actuel. Vous pouvez restreindre l’application, l’appareil ou la période pendant que la page reste ouverte ; la rouvrir rétablit ces valeurs par défaut. + +Avec toutes les applications sélectionnées, la section **Applications** apparaît après Modèles et Fournisseurs. Pour les appareils et la période choisis, elle indique la part du total des jetons, le nombre de jetons, le coût estimé au tarif catalogue de l’API, le nombre de requêtes et celui des requêtes dont l’utilisation a été mesurée, par application. Codex CLI, bureau et IDE sont regroupés sous Codex. Les anciens enregistrements sans attribution client sont **Non classés**, distincts de **Inconnu** et **Autre**. Sélectionner une seule application masque cette répartition. Un proxy qui ne fournit pas cette répartition l’affiche comme indisponible ; une répartition vide indique l’absence d’utilisation par application dans le rapport. + ### Filtrer les requêtes Le détail d’une requête privilégie la cause d’échec amont enregistrée plutôt qu’un message générique d’erreur du proxy. Le nombre d’envois physiques est toujours affiché lorsqu’il est valide ; une comptabilité absente ou invalide affiche **Unavailable**, et non zéro. **Retries** compte les envois après le premier uniquement lorsqu’une seule tentative entièrement réglée et cohérente est enregistrée. Les tentatives multiples, les comptabilités non résolues ou incohérentes et les normalisations avec perte omettent les retries au lieu d’en inventer. Une cause persistante inconnue revient au titre fondé sur le statut, sans attribution brute. **Upstream reason** affiche une condition fournisseur classée (par exemple limite de débit atteinte, quota ou crédits épuisés, fenêtre de contexte dépassée, emplacement non pris en charge, vérification du compte requise ou modèle indisponible), et **Failure detail** affiche un texte rédigé par le proxy, comme un délai d’inactivité ou une limite d’octets ; les nouvelles lignes ne conservent pas le texte d’erreur propre au fournisseur. D’anciens fichiers `usage.jsonl` peuvent encore le contenir, mais le tableau de bord l’écarte à la lecture. -Les filtres combinent interface, requêtes interceptées, fournisseur, modèle exact, statut, période, vitesse et identifiant de conversation dans le journal chargé. Les choix incluent les tentatives de repli ; les modèles ignorent la casse et les espaces externes, sans correspondance partielle. Un choix disparu revient à Tous. +Les journaux réunissent toutes les applications et affichent une pastille client contenant uniquement une icône sur chaque requête, à la place des onglets par application. Les clients reconnus utilisent leur logo SVG ; les clients inconnus ou autres utilisent une icône neutre en forme de point d’interrogation. Les noms des applications restent accessibles aux lecteurs d’écran, sans texte visible. L’absence d’attribution client n’implique ni Codex ni un échec de la requête. La pastille identifie l’application cliente indépendamment du fournisseur et du protocole ; ce n’est pas un filtre d’application. + +Les filtres combinent requêtes interceptées, fournisseur, modèle exact, statut, période, vitesse et identifiant de conversation dans le journal chargé. Les choix incluent les tentatives de repli ; les modèles ignorent la casse et les espaces externes, sans correspondance partielle. Un choix disparu revient à Tous. Les périodes de 15 minutes, une heure et un jour évoluent toutes les 30 secondes dans l’onglet Logs, même sans actualisation automatique. La vitesse mesure les jetons de sortie par seconde sur toute la durée : moins de 15, de 15 à moins de 50, ou au moins 50 ; les valeurs indisponibles sont exclues quand ce filtre est actif. Réussite : 2xx ; erreur : 4xx/5xx. -Le compteur compare les résultats au total chargé ; la réinitialisation restaure toutes les lignes. Aucun résultat diffère d’un journal vide. Flèches et Home/End pilotent le sélecteur d’interface. Aucun historique au-delà du journal chargé n’est interrogé. +Le compteur compare les résultats au total chargé ; la réinitialisation restaure toutes les lignes. Aucun résultat diffère d’un journal vide. Aucun historique au-delà du journal chargé n’est interrogé. ### Liens directs vers une section diff --git a/docs-site/src/content/docs/fr/reference/cli/providers-accounts.md b/docs-site/src/content/docs/fr/reference/cli/providers-accounts.md index 432161e84fe..2aeeaf1a3d1 100644 --- a/docs-site/src/content/docs/fr/reference/cli/providers-accounts.md +++ b/docs-site/src/content/docs/fr/reference/cli/providers-accounts.md @@ -129,7 +129,7 @@ Répertoriez et changez de compte de fournisseur et de pools de clés API via le la surface est : ```text -Usage: ocx account ... +Usage: ocx account ... list [provider] Codex account pool, OAuth accounts and API keys (identifiers shown masked as the API returns them). history openai [--limit <1-200>] Recent routing decisions for one Codex pool account. @@ -143,6 +143,7 @@ pause Hold an account out of automatic selection. resume Return a paused account to automatic selection. pause-exhausted Pause every account whose quota is spent. clear-cooldown Drop a cooldown the proxy set after an upstream failure. +reset-affinity [--json] Confirm all session bindings are cleared, even with no accounts; selection and pause state stay unchanged. Run on the Home; a Remote Link Child names the Home and sends no request. strategy [] Stratégie du pool ; least-loaded est réservé à Kiro. sticky [<1-100>] Requests a bound thread keeps on one account; omit the value to read it. priority [first|earlier|normal|later|last|-100..100|reset] Selection order; omit the value to read it. @@ -160,7 +161,7 @@ A selection-order change applies from the next unbound request and never moves a Les sous-commandes nécessitent que le proxy soit en cours d'exécution et résolvent automatiquement son port d'exécution enregistré, sauf `import-orca` : l'aperçu est purement local, et `import-orca --apply` exige que le proxy soit arrêté. Les opérations réussies se terminent 0. Utilisation invalide, un fournisseur ou un identifiant inconnu, un -proxy, ou un échec API se termine 1. Les champs d'informations d'identification sont affichés exactement comme la gestion API +proxy inaccessible ou un échec API renvoient normalement le code 1. Pour `reset-affinity`, HTTP 404 renvoie 4 et HTTP 409 renvoie 5 ; les autres échecs API et les résultats non confirmés renvoient 1. Les champs d'informations d'identification sont affichés exactement comme la gestion API les renvoie (y compris son masquage) ; Les clés API brutes et les jetons OAuth ne sont jamais restitués. Affichage les commodités sont synthétisées côté client, comme dans le tableau de bord : `main` désigne la connexion Codex App du groupe de comptes `openai`, les comptes OAuth sans adresse e-mail apparaissent sous la forme `Account N`, diff --git a/docs-site/src/content/docs/fr/reference/management-api.md b/docs-site/src/content/docs/fr/reference/management-api.md index 25f81a99073..3bf84f5a3b2 100644 --- a/docs-site/src/content/docs/fr/reference/management-api.md +++ b/docs-site/src/content/docs/fr/reference/management-api.md @@ -279,15 +279,18 @@ Tant qu’une liste initiale fiable n’est pas disponible, les requêtes PUT va | `GET /api/oauth/status` | Sonder le flux OAuth d'un fournisseur | 400 fournisseur inconnu | | `POST /api/oauth/logout` | Supprimer les informations d'identification du fournisseur sélectionné | 400 fournisseur inconnu ; `oauth_mutation_busy` | | `GET, DELETE /api/oauth/accounts` | Répertorier les comptes masqués ou supprimer un compte Les lignes Kiro ajoutent `autoSelectable` et un `skipReason` fermé en cas d’exclusion de la sélection automatique ; un compte actif unique peut encore servir. Le quota reste facultatif. | 400 invalide provider/id ; 404 compte manquant ; `oauth_mutation_busy` | -| `PUT /api/oauth/accounts/active` | Sélectionnez le compte OAuth actif | 400 invalide provider/account ; `oauth_mutation_busy` | +| `PUT /api/oauth/accounts/active` | Sélectionnez le compte OAuth actif | 400 invalide provider/account ; `oauth_mutation_busy`; 503 `affinity_reset_not_persisted` (Anthropic: `selectionCommitted: true`, `retryable: true`; sélection enregistrée ; renvoyer la même requête même si le compte est actif) | | `GET, PUT, PATCH /api/oauth/accounts/pool` | Lire ou mettre à jour la stratégie du pool OAuth Anthropic | 400 fournisseur non Anthropic ou stratégie invalide | | `POST /api/oauth/accounts/clear-cooldown` | Effacer le temps de recharge d'un compte OAuth | 400 invalide provider/account | | `PUT /api/oauth/accounts/alias` | Définir ou supprimer un alias de compte OAuth | 400 invalide provider/account/alias | -| `PUT /api/oauth/accounts/pause` | Suspendre/reprendre Anthropic ou un compte OAuth générique. Body `{ provider, accountId, paused }` ; la suspension du compte actif sélectionne un autre compte utilisable s’il existe. | 400 fournisseur non pris en charge ou body invalide ; 404 compte absent ; `oauth_mutation_busy` | +| `PUT /api/oauth/accounts/pause` | Suspendre/reprendre Anthropic ou un compte OAuth générique. Body `{ provider, accountId, paused }` ; la suspension du compte actif sélectionne un autre compte utilisable s’il existe. | 400 fournisseur non pris en charge ou body invalide ; 404 compte absent ; `oauth_mutation_busy`; 503 `affinity_reset_not_persisted` (Anthropic: `pauseCommitted: true`, `retryable: true`; état de suspension enregistré ; conserver le compte et cet état, ajouter `retryAffinityReset: true` et réessayer, même après un redémarrage du serveur) | | `GET, POST, DELETE /api/providers/keys` | Répertorier les clés de fournisseur masquées, en ajouter ou en activer une, ou en supprimer une | 400 saisie invalide ; 404 fournisseur ou clé manquante | | `PUT /api/providers/keys/active` | Sélectionnez la clé active d'un fournisseur | 400 saisie invalide ; 404 provider/key manquant | | `PUT /api/providers/keys/alias` | Définir ou supprimer un alias de clé de fournisseur | 400 saisie invalide ; 404 provider/key manquant | | `GET, POST, PATCH, DELETE /api/keys` | Répertorier, créer, modifier ou supprimer les clés d'admission du plan de données | 400 corps ou identifiant invalide ; 404 clé manquante | +| `PUT /api/oauth/accounts/affinity-reset` | Confirmer le reset des associations de conversations du provider sans exiger de compte. Body `{ provider }` ; succès : `affinityResetConfirmed: true`. CLI: `ocx account reset-affinity anthropic --json` (`anthropic2` supported). | 400 invalid body/provider; 503 `affinity_reset_not_persisted`, `retryable: true` | + +Le tableau de bord conserve l’avertissement tant que la publication du reset n’est pas confirmée, même si le compte a disparu. Un échec de chargement, une suspension ou une réauthentification requise conserve la tentative avec un message. Une fois l’absence confirmée par la liste, Réessayer appelle `PUT /api/oauth/accounts/affinity-reset` avec `{ provider }` et attend `affinityResetConfirmed: true` avant d’effacer l’avertissement. Cette opération supprime toutes les associations, y compris celles créées depuis le premier échec, sans sélectionner ni réactiver de compte ; une liste vide est acceptée. Une suppression avec un reset en attente envoie `retryAffinityReset=true`. En cas d’échec de publication, elle renvoie 503 `affinity_reset_not_persisted`, `deletionCommitted: true` et `retryable: true`. Le compte est déjà supprimé : réessayez le reset seul, pas la suppression. Une suppression ordinaire conserve les associations des comptes restants. Ce nettoyage est limité aux instances Anthropic OAuth enregistrées et conserve les contrôles d’authentification et d’origine de gestion. Les réponses qui répertorient les identifiants sont délibérément masquées. Les jetons d'accès OAuth et les clés API complètes des fournisseurs ne sont pas renvoyés aux clients du tableau de bord. diff --git a/docs-site/src/content/docs/guides/claude-code.md b/docs-site/src/content/docs/guides/claude-code.md index e8dd0e6f48a..d3e46fbbd37 100644 --- a/docs-site/src/content/docs/guides/claude-code.md +++ b/docs-site/src/content/docs/guides/claude-code.md @@ -78,7 +78,8 @@ inactive, while only active model-specific weekly limits affect that model famil The dashboard shows Pro, Max 5x, or Ultra beside Claude account quotas, using subscription metadata from the authenticated profile. Both `ultra` and `claude_max_20x` display as Ultra (the existing `ultra` and `max_20x` tier aliases are retained). Unfamiliar validated tiers retain their original name without -failing the usage read. Claude account rows use ordinal labels and omit account IDs and emails. +failing the usage read. Claude account rows prefer your alias, then a masked email, and +fall back to a numbered label when no safe identity is available. You can log in multiple Claude accounts via the Providers dashboard (`ocx login anthropic` / add-account). By default every request uses the **active** account only. diff --git a/docs-site/src/content/docs/guides/desktop-app.md b/docs-site/src/content/docs/guides/desktop-app.md index 73e3822f56d..33a593d15e4 100644 --- a/docs-site/src/content/docs/guides/desktop-app.md +++ b/docs-site/src/content/docs/guides/desktop-app.md @@ -105,7 +105,8 @@ when they verify that the app runs its bundled proxy, even without recorded owne CLI service repairs for that runtime. On macOS and Linux, turn on **Start at Login** in the OpenCodex menu instead of installing a background service. Startup safety credits the app only when its login registration is verified; if it cannot be verified, follow the Desktop guidance in status. -The Startup page then labels the Desktop row **Running · Start at Login not verified**. +The Startup page shows a quiet **Start at Login not verified** note when the app's +registration cannot be checked. A verified disabled registration is shown as action required. On macOS the menu's **Start at Login** item is checked only when Startup safety would verify the same registration. Otherwise the item is unchecked and its label says why: **disabled by macOS** opens System Settings → General → Login Items & Extensions, where @@ -174,7 +175,7 @@ On Linux, the pinned autostart backend writes the login entry to `~/.config/autostart/OpenCodex.desktop`, even when `$XDG_CONFIG_HOME` is set. Startup safety reads that entry and the desktop install-id under `~/.config`. When `$XDG_CONFIG_HOME` points elsewhere, the login session searches a different autostart -directory, so startup safety stays **At risk** instead of crediting the entry. +directory, so startup safety does not credit the entry as restart protection. The entry counts only while it is not marked `Hidden=true` or `X-GNOME-Autostart-enabled=false`, and has no `OnlyShowIn`, `NotShowIn`, or `TryExec` condition. Its `Exec` must be an unquoted absolute path to `opencodex-desktop`, without @@ -183,12 +184,15 @@ executable must still be named `opencodex-desktop`, with its bundled `ocx` besid Startup safety reads the full evidence chain twice and grants protection only when both reads agree. -AppImage installations remain **At risk**: the autostart backend registers the outer +AppImage installations do not receive verified Desktop protection: the autostart backend registers the outer AppImage path, while the live desktop process runs inside its mount. Startup safety cannot verify that relationship and does not credit AppImage protection. -A missing or stale check remains **At risk**. If the desktop app owns the proxy but -protection cannot be verified, reopen OpenCodex and check **Start at Login**. Service -and launcher installation or repair stays disabled while that ownership remains; +A missing or stale check does not earn restart-protection credit. If Desktop owns or +supervises the proxy, the page shows an informational note and asks you to check +**Start at Login** in the OpenCodex menu. Service installation or repair stays disabled +while that ownership remains. The launcher shim can still be installed when diagnostics are fresh; +its Install button is disabled while diagnostics are stale. On Linux without a tray, inspect +`~/.config/autostart/OpenCodex.desktop` and the conditions described above. The command `ocx restore` is still available to undo Codex routing. Normal desktop updates replace the bundled CLI with the fixed startup probe. No local diff --git a/docs-site/src/content/docs/guides/ollama-clients.md b/docs-site/src/content/docs/guides/ollama-clients.md index 9875a96a051..7e4f1534cad 100644 --- a/docs-site/src/content/docs/guides/ollama-clients.md +++ b/docs-site/src/content/docs/guides/ollama-clients.md @@ -45,6 +45,11 @@ Every other `/api/*` path stays part of the opencodex management API. - `options`: `temperature`, `top_p`, `num_predict` and `stop`. - `format`: `"json"` only. +Each `/api/chat` request accepts at most **1,024 tool calls in total** across all assistant messages, +including calls that already have tool results. Above this limit, the request returns HTTP 400 +before reaching a provider. Shorten the conversation history and resend it, keeping each retained +tool result together with its corresponding assistant tool call. + Anything else is rejected with HTTP 400 instead of being ignored. That includes `think`, `keep_alive`, `suffix`, `raw`, `template`, `context`, other `options`, JSON-schema `format` and image URLs. Ollama's model-management and embedding endpoints are not provided. diff --git a/docs-site/src/content/docs/guides/providers.md b/docs-site/src/content/docs/guides/providers.md index a6cba2299fc..d28873ac404 100644 --- a/docs-site/src/content/docs/guides/providers.md +++ b/docs-site/src/content/docs/guides/providers.md @@ -162,6 +162,33 @@ disables shared pacing. If Mistral's retry time exceeds `retryOn429.maxIntervalMs`, the proxy returns the 429 and retry hint rather than replaying early. Context windows and per-model compaction preferences remain independent. +Mistral accepts a different set of `reasoning_effort` values for each model and refuses the +rest with HTTP 400 (or 422 for `ultra`). The preset ships the accepted rungs as registry +`modelReasoningEfforts` rows. Client exports such as `ocx export --client opencode` offer only +these variants. The Codex catalog still adds its synthetic `ultra` rung, plus `max` where a +model's list omits it, and requests clamp those to the highest listed rung: + +| Model | Efforts | +|---|---| +| `zai-glm-5-3`, `zai-glm-5-3-flash` | `low`, `high`, `max` | +| `mistral-large-4`, `mistral-large-4-0` | `low`, `medium`, `high` | +| `deepseek-v4-1-flash-1017` | `low`, `high`, `xhigh`, `max` | + +The GLM and Large 4 rows follow [Mistral's reasoning guide](https://docs.mistral.ai/capabilities/reasoning). +Mistral documents no values for DeepSeek V4.1 Flash 1017, so its row is the set measured to +answer HTTP 200 in November 2026 and may change. A `modelReasoningEfforts` entry of your own +for a model replaces its row. + +On native Chat Completions, for a model with reasoning enabled, a caller's `ultra` is sent as `max`, or as the model's highest +declared rung below `max` (`high` on Large 4). This applies to every `openai-chat` provider +unless the model's declared ladder lists `ultra` itself. A `reasoningEffortMap` alias for `max` +applies to the folded value, and `__omit__` drops the effort field. On models with independent +thinking and effort controls, the effort still selects the thinking switch; legacy thinking-toggle +wires keep their existing discriminator mapping. Models with reasoning disabled (for +example `noReasoningModels`) drop `reasoning_effort` entirely. Other caller values are forwarded +as sent unless an effort pin or a qualifying effort cap rewrites them; rewritten values also go +through the provider's wire mapping. + When round-robin key selection is enabled, queued requests recheck key cooldowns before sending and acquire pacing capacity for any replacement key. This applies to native Chat, Messages, Responses and compact. Waiting for replacement-key capacity does not consume the upstream response-header timeout. Per-key pacing and round-robin use guarded HTTP/SSE even when upstream WebSockets are enabled; WebSocket-only steering and injection require a route without those key policies. This does not change the saved active key. An established upstream turn keeps its credential; later sends are refused if that key cools. Pre-output key rejection can recover to a healthy key within the existing request limit. Direct Images and compact requests record genuine 401/429 cooldowns without replaying that paid POST. ### Anthropic API-key credit exhaustion @@ -1454,6 +1481,7 @@ OpenCodex provides official adapter support for Qoder through the `qoder` (Globa - **Model Discovery:** `qoder --list-models` is the authoritative entitlement roster for the current PAT. The cache is bound to an irreversible fingerprint of the token, so switching accounts never reuses another account's roster. If discovery fails, the provider degrades to a stale cache and then the documented static seed. The CLI starts a full agent runtime first, so background catalog discovery allows each run up to 30 seconds, and concurrent callers of the same provider share one run per token. The dashboard connection test waits at most 8 seconds: past that it reports the provider as still discovering rather than failed, and when the run succeeds the catalog is re-synced without a proxy restart. Codex sessions that are already running keep their previous model list until they restart: restart Codex, or run `ocx system codex-restart --yes`, which restarts Codex app-servers (the CLI reference describes what it keeps and what it relaunches). - **Token usage:** Qoder CLI reports zero token counts unless its usage switch is on, so each preset turns it on in its scoped CLI child environment: `QODER_EXPOSE_TOKEN_USAGE=true` for Global and `QODERCN_EXPOSE_TOKEN_USAGE=true` for CN, which reads only its own prefix. - **Tool Ownership:** The CLI runs single-turn `stream-json` with `--tools ""`, `--strict-mcp-config`, setting sources disabled, and session persistence disabled, so Codex keeps exclusive tool ownership. Client-declared tools are exposed through an isolated request-scoped MCP server. +- **Tool names:** Qoder Global and Qoder CN accept each selected tool's exact client name or its advertised MCP name. Repeating the same call ID and arguments under either spelling returns the tool call only once. Unknown or unselected names are rejected. If one selected tool's client name equals another selected tool's MCP name, the request fails with `tool_catalog_invalid` before the CLI starts. Select one of those tools or rename it to remove the ambiguity. Tool execution remains with the client. - **Output guard:** If the CLI echoes OpenCodex tool-call/result replay envelopes into assistant text, the turn fails with `vendor_scaffold_detected` (502) instead of returning the envelope as a successful answer, and neither reasoning nor tool calls following the envelope are forwarded. Reasoning starts a fresh replay-text block, so earlier prose or code fences cannot hide an envelope in later text. If the CLI's turn already ended with its own error, that error is reported instead, still without the envelope. The proxy does not retry that failure automatically. Ordinary prose and closed fenced examples follow the shared envelope recognizer; an unverified overflowing fenced example is refused. Assistant text preserves Unicode characters split across CLI frames; unpaired surrogates are replaced with U+FFFD. - **Image Input:** Global continues forwarding supported images through the shared projection without a per-model vision allowlist. CN retains its vision-sidecar defaults. Its raw-image route admits only one canonical base64 PNG of at most 1 MiB decoded, 8192 pixels per edge, and 16 million total pixels in a single user turn with `DeepSeek-Flash` or `Qwen3.8-Flash`. Use `qoderclicn 1.1.70`: [issue #7516](https://github.com/lidge-jun/opencodex/issues/7516) reports Windows 11 x64 live success for those models on both `--attachment` and inline `--input-format stream-json --output-format stream-json` stdin; OpenCodex uses only stdin. The adapter validates base64 length/padding, decoded PNG signature and basic IHDR fields before CLI discovery; it does not decompress pixels or validate every PNG chunk/CRC. These ceilings are OpenCodex resource policy, not verified vendor maxima: the reported live evidence covers a 96×96 PNG, whose RGBA raster is only 36 KiB. Larger inputs require separate verification. Other CLI versions have not been verified, and the adapter does not probe the installed version. CN refuses raw URL images, other image formats/models, multiple images, tool-result images and image-bearing history/multi-turn replay before starting the CLI with `unsupported_input_modality`; use the vision sidecar for those inputs. Injected CLI tests cover serialization and cancellation without claiming live upstream support. Qoder's supported projection keeps image blocks in conversation order and marks unsupported references as `[image omitted: unsupported reference]`. Global stateless replay can still associate historical images unreliably with their original text; CN does not enable that unverified replay surface. - **PAT recovery:** For both Qoder presets, a multi-key API-key pool can switch to another PAT on a supported authentication rejection or account-level quota exhaustion, but only before response content or an event that makes replay unsafe. Recovery requires Qoder-specific classification and respects credential-hop and send budgets; an HTTP 401/429 or quota message alone is not permission to replay. Single-key configurations do not rotate. Sharing the CLI runner does not enable this API-key recovery for CodeBuddy or self-authenticated Claude CLI. diff --git a/docs-site/src/content/docs/guides/remote-link.md b/docs-site/src/content/docs/guides/remote-link.md index a162851a342..25aa0d8fd11 100644 --- a/docs-site/src/content/docs/guides/remote-link.md +++ b/docs-site/src/content/docs/guides/remote-link.md @@ -77,7 +77,7 @@ On a Child, `GET /healthz` only says that OpenCodex is running. `GET /readyz` (a The reason `client_state_changed` means the running Child's saved connection is missing or now names another owner or link. Its machine and link status endpoints also return `503` with this reason. Stop or restart the Child and check its connection before re-linking; this readiness result does not itself disconnect the relay. -If `ocx connect status --json` reports `disconnected` but a leftover service token prevents a new link, inspect the situation on the Child. After confirming that preserving the existing token files is intended, run `ocx disconnect --orphaned-token --yes`. It refuses a connected or pending client and any live client runtime or unverified runtime state. It also refuses a Hub, a non-loopback bind, a configured or matching admission secret, and an installed or unverified service: these can use the token for admission. On a Hub, manage the credential through the Hub service; otherwise inspect the bind, API keys, token environment and `ocx service status`, and remove or migrate a service only if intended before retrying. It moves the token and any rotation backup into a private directory under the OpenCodex config directory, prints the location, and does not infer that either token belonged to the link. On Windows, recovery requires hard-link support to preserve file identity while closing handles; if it reports an incomplete backup, inspect the reported directory and original files before retrying. If recovery was interrupted and a retry refuses with `unsafe token file` because another hard link exists, remove the leftover `.service-api-token.identity` (or `.service-api-token.prev.identity`) file from the `orphaned-token-*` folder after checking that the original token file is still in place, then retry. Stop OpenCodex to restore: move the saved files back only when the original names are absent. The command does not revoke a Home key or restore missing client configuration. +If `ocx connect status --json` reports `disconnected` but a leftover service token prevents a new link, inspect the situation on the Child. After confirming that preserving the existing token files is intended, run `ocx disconnect --orphaned-token --yes`. It refuses a connected or pending client and any live client runtime or unverified runtime state. It also refuses a Hub, a non-loopback bind, a configured or matching admission secret, and an installed or unverified service: these can use the token for admission. On a Hub, manage the credential through the Hub service; otherwise inspect the bind, API keys, token environment and `ocx service status`, and remove or migrate a service only if intended before retrying. It moves the token and any rotation backup into a private directory under the OpenCodex config directory, prints the location, and does not infer that either token belonged to the link. On Windows, recovery requires hard-link support to preserve file identity while closing handles; if it reports an incomplete backup, inspect the reported directory and original files before retrying. If recovery was interrupted and a retry refuses with `unsafe token file` because another hard link exists, check the `orphaned-token-*` folders for a leftover `.service-api-token.identity` (or `.service-api-token.prev.identity`) file. Remove it only if it is a hard link of the current token file (the same file, not just the same name); keep any other pin, because it may be the only copy of an older token. Then inspect any remaining hard links to the token file before retrying. Stop OpenCodex to restore: move the saved files back only when the original names are absent. The command does not revoke a Home key or restore missing client configuration. To confirm which Home is serving a Child, compare the `x-opencodex-served-by` header on the Child's `GET /v1/models` with the `instanceId` in the Home's `GET /healthz`. The Child passes the Home's header through unchanged, so the two match while the link works. The value is an opaque id, never a hostname; see [Home identity and the served-by header](/reference/management-api/#home-identity-and-the-served-by-header). diff --git a/docs-site/src/content/docs/guides/sidecars.md b/docs-site/src/content/docs/guides/sidecars.md index ed7f778add1..dec347dbc52 100644 --- a/docs-site/src/content/docs/guides/sidecars.md +++ b/docs-site/src/content/docs/guides/sidecars.md @@ -37,6 +37,12 @@ a missing credential produces no sidecar plan and the request takes the normal r ## Web-search sidecar +Anthropic search-model calls and their connection-reset retries also consume the logical request's +shared model-send and token allowances on routed Responses, including RunTurn adapters and the +passthrough search bridge. `maxSearchesPerTurn` does not grant extra model sends. If admission is +exhausted, that search returns an error result without contacting Anthropic. Helper admission does +not spend the routed model's final-recovery reserve. Standalone `/v1/alpha/search` keeps its existing behavior. + When Codex requests hosted `web_search` for a non-passthrough routed model, opencodex: 1. **Drops** the hosted `web_search` tool and exposes a synthetic `web_search(query)` function tool diff --git a/docs-site/src/content/docs/guides/web-dashboard.md b/docs-site/src/content/docs/guides/web-dashboard.md index da81f82f50c..5dd7a99df8f 100644 --- a/docs-site/src/content/docs/guides/web-dashboard.md +++ b/docs-site/src/content/docs/guides/web-dashboard.md @@ -171,6 +171,20 @@ failover can still select another usable account with the pool off. A committed selection updates the dashboard immediately; account changes do not wait for the quota refresh timer. Requests already sent upstream retain their original credentials. +### Usage by app + +Usage opens with **All apps**, **All devices**, and **30d** selected within +the current access scope. You can narrow the app, device, or time range while the page is +open; reopening it restores these defaults. + +With **All apps** selected, **Apps** appears after Models and Providers. It shows each app's +share of total tokens, token count, estimated API list-price cost, request count, and measured +request count for the selected devices and time range. Codex CLI, desktop, and IDE usage is +grouped under Codex. Historical records without client attribution appear as **Unclassified**, +separately from **Unknown** and **Other**. Selecting one app hides this breakdown. A proxy +that does not supply the breakdown shows it as unavailable; an empty breakdown means no app +usage in the report. + ### Filtering request logs Request details prefer a recognized upstream failure cause over generic proxy-error copy. Physical sends are always shown when valid; missing or invalid accounting shows **Unavailable**, not zero. **Retries** counts sends after the first only when one fully settled, consistent attempt is recorded. Multi-attempt, unresolved, inconsistent, or lossy-normalized records omit retries instead of inventing them. Unknown persisted causes use the status-based headline without raw attribution. **Upstream reason** shows a classified provider condition (for example rate limited, quota or credits exhausted, context window exceeded, location not supported, account verification required, or model unavailable), and **Failure detail** shows text written by the proxy, such as a stall timeout or byte-cap limit; new rows do not store the provider's own error text. Older `usage.jsonl` files may still contain it, but the dashboard drops it when reading them. @@ -183,7 +197,13 @@ its request and attempt failure cause is `transport-ambiguous`, with resend perm A Codex move to another account after HTTP 402 appears as **Account quota exhausted (402)** in the attempt's recovery details and counts as a quota recovery in exported metrics. -Logs filters combine surface, intercepted requests, provider, exact model, status, time, +Logs shows all apps together, with an icon-only client chip on each request instead of +app tabs. Recognized clients use their SVG logos; unknown or other clients use a neutral +question-mark icon. App names remain available to screen readers without visible text. +Missing client attribution does not imply Codex or a request failure. The chip identifies +the client app, independently of the provider and protocol, and is not an app filter. + +Logs filters combine intercepted requests, provider, exact model, status, time, speed, and conversation ID over the currently loaded request ring. Provider and model choices also include fallback attempts; model matching ignores case and surrounding spaces but does not match partial names. Choices that disappear from the ring reset to All. @@ -215,9 +235,8 @@ Historical logs are not rewritten and missing generation timestamps are not inve tok/s column and speed filter above are not affected. Active filters show the matching count out of the loaded total. Reset filters restores all -rows and returns keyboard focus to the All surface control; “No matching requests” -differs from an empty log ring. Use arrow keys or Home/End in -the surface selector. These controls do not query historical records beyond the loaded ring. +rows; “No matching requests” differs from an empty log ring. These controls do not query +historical records beyond the loaded ring. The language picker includes **Português** (Brazilian Portuguese, `pt-BR`). Portuguese browser languages select it automatically unless a supported language preference is already saved. diff --git a/docs-site/src/content/docs/ja/guides/ollama-clients.md b/docs-site/src/content/docs/ja/guides/ollama-clients.md index da7f99a19a8..fb6f55f26fc 100644 --- a/docs-site/src/content/docs/ja/guides/ollama-clients.md +++ b/docs-site/src/content/docs/ja/guides/ollama-clients.md @@ -43,6 +43,10 @@ curl "$OLLAMA_HOST/api/tags" - `options`: `temperature`、`top_p`、`num_predict`、`stop`。 - `format`: `"json"` のみ。 +各 `/api/chat` リクエストでは、すべての assistant メッセージを合わせて **ツール呼び出しは最大 1,024 件**です。 +結果がすでに返された呼び出しも含みます。上限を超えると、プロバイダーに送信される前に HTTP 400 が返されます。 +会話履歴を短くして再送してください。残すツール結果と、それに対応する assistant のツール呼び出しは必ず一緒に残します。 + それ以外は無視されず、HTTP 400 で拒否されます。`think`、`keep_alive`、`suffix`、`raw`、`template`、 `context`、その他の `options`、JSON スキーマ形式の `format`、画像 URL が該当します。Ollama のモデル管理および 埋め込みエンドポイントは提供されません。 diff --git a/docs-site/src/content/docs/ja/guides/web-dashboard.md b/docs-site/src/content/docs/ja/guides/web-dashboard.md index 2294789ce8b..8bc23e1faae 100644 --- a/docs-site/src/content/docs/ja/guides/web-dashboard.md +++ b/docs-site/src/content/docs/ja/guides/web-dashboard.md @@ -69,15 +69,23 @@ bun run dev:gui 使用量、ダッシュボード、プロバイダー画面、プロバイダーカタログ、API キー画面は、読み取れる記録がなくても除外された使用履歴の警告を表示します。回数、日付、使用順位は読み取れる記録のみを反映します。履歴が不完全な場合はモデルの使用回数順の保存を拒否します。別の順序を選ぶか、履歴を修復してから再試行してください。 +### アプリ別の使用量 + +Usage は現在のアクセス範囲で、**すべてのアプリ**・**すべてのデバイス**・**30日**を初期選択します。ページを開いている間はアプリ、デバイス、期間を絞り込めますが、開き直すと初期値に戻ります。 + +すべてのアプリを選択すると、Models と Providers の後に **アプリ**の内訳が表示されます。選択したデバイスと期間について、アプリごとの総トークンに占める割合、トークン数、API 定価換算の推定費用、リクエスト数、使用量を測定できたリクエスト数を示します。Codex CLI・デスクトップ・IDE は Codex にまとめます。クライアント情報のない過去の記録は **未分類**とし、**不明**・**その他**と区別します。アプリを一つ選ぶと内訳は非表示になります。内訳を提供しないプロキシでは利用不可と表示し、空の内訳ではレポートにアプリ使用量がないと表示します。 + ### リクエストログの絞り込み リクエスト詳細では、一般的なプロキシエラーの文言よりも、記録されたアップストリームの失敗原因を優先して表示します。物理的なアップストリーム送信数は有効な場合に常に表示し、会計情報がない、または無効な場合は 0 ではなく **Unavailable** と表示します。**Retries** は、完全に精算され整合する単一の試行が記録されている場合に限り、最初の送信以降の追加送信回数として表示します。複数の試行、未解決・不整合な会計、または損失のある attempt 正規化では、再試行回数を推測せず非表示にします。未知の永続化された原因は、状態に基づく見出しに戻ります。**Upstream reason** には分類されたプロバイダーの状態(レート制限、クォータまたはクレジットの枯渇、コンテキストウィンドウ超過、未対応の地域、アカウント確認が必要、モデル利用不可など)を、**Failure detail** にはストールタイムアウトやバイト上限などプロキシが記述したテキストを表示します。新しい行にはプロバイダー自身のエラーテキストを保存しません。古い `usage.jsonl` ファイルには残っている場合がありますが、ダッシュボードは読み込み時にそれを破棄します。 -Logsではサーフェス、インターセプトされたリクエスト、プロバイダー、完全なモデル名、ステータス、時間、速度、会話IDを組み合わせて、読み込み済みログを絞り込みます。選択肢にはフォールバック試行も含まれます。モデル名は大文字小文字と前後の空白を無視しますが、部分一致ではありません。ログから消えた選択肢は全件に戻ります。 +Logs はアプリ別タブの代わりにすべてのアプリのリクエストをまとめ、各リクエストにアイコンのみのクライアントチップを表示します。認識されたクライアントには SVG ロゴを、不明またはその他のクライアントには中立的な疑問符アイコンを使用します。アプリ名は画面に文字として表示せず、スクリーンリーダーには引き続き提供します。クライアント情報がないことは、Codex やリクエスト失敗を意味しません。チップはプロバイダーやプロトコルとは別にクライアントアプリを示し、アプリフィルターではありません。 + +Logsではインターセプトされたリクエスト、プロバイダー、完全なモデル名、ステータス、時間、速度、会話IDを組み合わせて、読み込み済みログを絞り込みます。選択肢にはフォールバック試行も含まれます。モデル名は大文字小文字と前後の空白を無視しますが、部分一致ではありません。ログから消えた選択肢は全件に戻ります。 時間は直近15分・1時間・1日で、Logsタブでは自動更新をオフにしても30秒ごとに更新します。速度はリクエスト全体の時間あたりの毎秒出力トークン数で、15未満、15以上50未満、50以上です。速度フィルター中は測定不能な行を除外します。成功は2xx、エラーは4xx/5xxです。 -一致件数と読み込み総数を表示し、リセットで全行を復元します。一致なしと空ログを区別します。サーフェスは矢印キーとHome/Endで操作できます。読み込み範囲外の履歴は検索しません。 +一致件数と読み込み総数を表示し、リセットで全行を復元します。一致なしと空ログを区別します。読み込み範囲外の履歴は検索しません。 ### セクションへのリンク diff --git a/docs-site/src/content/docs/ja/reference/cli/providers-accounts.md b/docs-site/src/content/docs/ja/reference/cli/providers-accounts.md index 24c16ec5c69..5ba526d7f2b 100644 --- a/docs-site/src/content/docs/ja/reference/cli/providers-accounts.md +++ b/docs-site/src/content/docs/ja/reference/cli/providers-accounts.md @@ -103,7 +103,7 @@ ocx login anthropic 実行中のプロキシを介してプロバイダー アカウントと API キー プールを一覧表示し、切り替えます。出荷されたヘルプ画面は次のとおりです。 ```text -Usage: ocx account ... +Usage: ocx account ... list [provider] Codex account pool, OAuth accounts and API keys (identifiers shown masked as the API returns them). history openai [--limit <1-200>] Recent routing decisions for one Codex pool account. @@ -117,6 +117,7 @@ pause Hold an account out of automatic selection. resume Return a paused account to automatic selection. pause-exhausted Pause every account whose quota is spent. clear-cooldown Drop a cooldown the proxy set after an upstream failure. +reset-affinity [--json] Confirm all session bindings are cleared, even with no accounts; selection and pause state stay unchanged. Run on the Home; a Remote Link Child names the Home and sends no request. strategy [] Pool placement strategy; omit the value to read it. sticky [<1-100>] Requests a bound thread keeps on one account; omit the value to read it. priority [first|earlier|normal|later|last|-100..100|reset] Selection order; omit the value to read it. @@ -131,7 +132,7 @@ main Manage the Codex App logi Codex pool selection applies to the next request after clearing existing affinity; in-flight requests keep their captured account. ``` -サブコマンドはプロキシが実行されている必要があり、記録されたランタイムポートを自動解決します。ただし `import-orca` は例外で、プレビューはローカルのみ、`import-orca --apply` はプロキシの停止が必要です。操作が成功した場合は 0 で終了します。無効な使用法、不明なプロバイダーまたはアカウント/キー ID、到達不能なプロキシ、または API エラーが発生した場合は 1 で終了します。資格情報フィールドは、管理 API が返したとおりに表示されます (マスキングを含む)。生の API キーと OAuth トークンは決して返されません。表示の利便性は、ダッシュボードと同様にクライアント側で合成されます。`main` は、`openai` アカウント プール内の Codex アプリ ログインの CLI エイリアスであり、電子メールのない OAuth アカウントは `Account N` として表示され、プラン/ラベル列はプラン、マスクされた電子メール、ラベル、およびマスクされたキーにわたってフォールバックされます。 +サブコマンドはプロキシが実行されている必要があり、記録されたランタイムポートを自動解決します。ただし `import-orca` は例外で、プレビューはローカルのみ、`import-orca --apply` はプロキシの停止が必要です。操作が成功した場合は 0 で終了します。無効な使用法、不明なプロバイダーまたはアカウント/キー ID、到達不能なプロキシ、または API エラーが発生した場合は通常 1 で終了します。ただし `reset-affinity` は HTTP 404 で終了コード 4、HTTP 409 で終了コード 5 を返し、その他の API エラーや未確認の結果では 1 を返します。資格情報フィールドは、管理 API が返したとおりに表示されます (マスキングを含む)。生の API キーと OAuth トークンは決して返されません。表示の利便性は、ダッシュボードと同様にクライアント側で合成されます。`main` は、`openai` アカウント プール内の Codex アプリ ログインの CLI エイリアスであり、電子メールのない OAuth アカウントは `Account N` として表示され、プラン/ラベル列はプラン、マスクされた電子メール、ラベル、およびマスクされたキーにわたってフォールバックされます。 `--json` アカウント行では、次の一般的な形状が使用されます (オプションのフィールドが使用できない場合は省略されます)。 diff --git a/docs-site/src/content/docs/ja/reference/management-api.md b/docs-site/src/content/docs/ja/reference/management-api.md index 86e5f8c1831..dff47bd756d 100644 --- a/docs-site/src/content/docs/ja/reference/management-api.md +++ b/docs-site/src/content/docs/ja/reference/management-api.md @@ -247,15 +247,18 @@ Aside プロファイルの変更はこの場合でも一つだけ保存しま | `GET /api/oauth/status` | 1 つのプロバイダーの OAuth フローをポーリングする | 400 不明なプロバイダー | | `POST /api/oauth/logout` |選択したプロバイダー資格情報を削除します | 400 不明なプロバイダー。 `oauth_mutation_busy` | | `GET, DELETE /api/oauth/accounts` | マスクされたアカウントを一覧表示するか、アカウントを 1 つ削除する Kiro の行には自動選択の `autoSelectable` と、除外時には閉じた集合の `skipReason` が含まれます。アクティブな単一アカウントは送信を続けられ、クォータ取得は任意です。 | 400 無効なプロバイダー/ID。 404 アカウントがありません。 `oauth_mutation_busy` | -| `PUT /api/oauth/accounts/active` |アクティブな OAuth アカウントを選択します | 400 無効なプロバイダー/アカウント。 `oauth_mutation_busy` | +| `PUT /api/oauth/accounts/active` |アクティブな OAuth アカウントを選択します | 400 無効なプロバイダー/アカウント。 `oauth_mutation_busy`; 503 `affinity_reset_not_persisted` (Anthropic: `selectionCommitted: true`, `retryable: true`; アカウント選択は保存済み。すでに有効でも同じリクエストを再送信) | | `GET, PUT, PATCH /api/oauth/accounts/pool` | Anthropic OAuth プール ポリシーの読み取りまたは更新 | 400 非 Anthropic プロバイダーまたは無効なポリシー | | `POST /api/oauth/accounts/clear-cooldown` | 1 つの OAuth アカウントのランタイム クールダウンをクリアする | 400 無効なプロバイダー/アカウント | | `PUT /api/oauth/accounts/alias` | OAuth アカウント エイリアスを設定またはクリアする | 400 無効なプロバイダー/アカウント/エイリアス | -| `PUT /api/oauth/accounts/pause` | Anthropic または汎用 OAuth アカウントを一時停止・再開。Body `{ provider, accountId, paused }`。アクティブなアカウントを停止すると、利用可能な別のアカウントがあれば切り替えます。 | 400 未対応のプロバイダーまたは無効な body;404 アカウントなし;`oauth_mutation_busy` | +| `PUT /api/oauth/accounts/pause` | Anthropic または汎用 OAuth アカウントを一時停止・再開。Body `{ provider, accountId, paused }`。アクティブなアカウントを停止すると、利用可能な別のアカウントがあれば切り替えます。 | 400 未対応のプロバイダーまたは無効な body;404 アカウントなし;`oauth_mutation_busy`; 503 `affinity_reset_not_persisted` (Anthropic: `pauseCommitted: true`, `retryable: true`; 一時停止状態は保存済み。同じアカウントと停止状態を維持し、`retryAffinityReset: true` を追加して再送信。サーバー再起動後もリセットを確認) | | `GET, POST, DELETE /api/providers/keys` |マスクされたプロバイダー キーを一覧表示し、1 つを追加/アクティブ化するか、1 つを削除します。 400 無効な入力。 404 プロバイダー/キーがありません | | `PUT /api/providers/keys/active` |プロバイダーのアクティブなキーを選択します | 400 無効な入力。 404 プロバイダー/キーがありません | | `PUT /api/providers/keys/alias` |プロバイダー キー エイリアスを設定またはクリアする | 400 無効な入力。 404 プロバイダー/キーがありません | | `GET, POST, PATCH, DELETE /api/keys` |データ プレーン アドミッション キーの一覧表示、作成、編集、または削除 | 400 無効な本文/ID。 404 キーがありません | +| `PUT /api/oauth/accounts/affinity-reset` | アカウントを必要とせず provider 全体の会話接続リセットの保存を確認します。Body `{ provider }`。成功時 `affinityResetConfirmed: true`. CLI: `ocx account reset-affinity anthropic --json` (`anthropic2` supported). | 400 invalid body/provider; 503 `affinity_reset_not_persisted`, `retryable: true` | + +ダッシュボードは、対象アカウントが消えてもリセットの保存が確認されるまで警告を保持します。一覧の読み込み失敗や、アクティブアカウントの一時停止・再認証時も通知と再試行を保持します。一覧で不在を確認した後の再試行は、`{ provider }` で `PUT /api/oauth/accounts/affinity-reset` を呼び、`affinityResetConfirmed: true` を受け取った場合のみ警告を消します。この操作は最初の失敗後に作られた接続を含む全接続を消しますが、アカウントの選択・再開は行わず、一覧が空でも実行できます。未確認のリセットがある状態での削除は `retryAffinityReset=true` を送り、保存失敗時は 503 `affinity_reset_not_persisted`、`deletionCommitted: true`、`retryable: true` を返します。アカウントは削除済みなので、削除ではなくリセット専用 API を再試行してください。通常の削除は残るアカウントの接続を保持します。対象は登録済み Anthropic OAuth インスタンスに限り、既存の管理認証とオリジン検査を維持します。 資格情報リストの応答は意図的にマスクされます。 OAuth アクセス トークンと完全なプロバイダー API キーはダッシュボード クライアントに返されません。 diff --git a/docs-site/src/content/docs/ko/guides/desktop-app.md b/docs-site/src/content/docs/ko/guides/desktop-app.md index 37ff66aba54..6da1eded1fa 100644 --- a/docs-site/src/content/docs/ko/guides/desktop-app.md +++ b/docs-site/src/content/docs/ko/guides/desktop-app.md @@ -81,7 +81,8 @@ macOS, Linux, Windows에서 데스크톱 앱이 번들 프록시를 실행하는 설치하는 대신 OpenCodex 메뉴에서 **Start at Login**을 켜세요. 앱의 로그인 등록이 확인되어야 재시작 후 보호가 적용됩니다. 등록을 확인할 수 없으면 status에 표시되는 데스크톱 안내를 따르세요. 실행 관계 확인은 소유권을 생성하지 않습니다. -이때 시작 안전 페이지의 데스크톱 앱 행에는 **실행 중 · 로그인 시 시작 미확인**이 표시됩니다. +등록을 확인할 수 없으면 시작 안전 페이지에 **로그인 시 시작 미확인** 안내가 표시됩니다. +꺼진 상태가 확인되고 다른 보호가 없으면 조치가 필요하다고 표시합니다. macOS에서 메뉴의 **Start at Login** 항목은 시작 안전 페이지도 같은 등록을 확인할 때만 체크됩니다. 그렇지 않으면 체크가 해제된 채 이유가 라벨에 붙습니다. **disabled by macOS**는 시스템 설정 → 일반 → 로그인 항목 및 확장 프로그램을 열며, 거기서 OpenCodex가 로그인 시 diff --git a/docs-site/src/content/docs/ko/guides/ollama-clients.md b/docs-site/src/content/docs/ko/guides/ollama-clients.md index 3ffd8c49749..26bb2eb6a2b 100644 --- a/docs-site/src/content/docs/ko/guides/ollama-clients.md +++ b/docs-site/src/content/docs/ko/guides/ollama-clients.md @@ -43,6 +43,10 @@ curl "$OLLAMA_HOST/api/tags" - `options`: `temperature`, `top_p`, `num_predict`, `stop`. - `format`: `"json"`만 지원합니다. +각 `/api/chat` 요청은 모든 assistant 메시지에 걸쳐 **도구 호출을 합계 1,024개까지** 허용합니다. +이미 결과가 있는 호출도 합계에 포함됩니다. 한도를 넘으면 프로바이더로 보내기 전에 HTTP 400을 반환합니다. +대화 이력을 줄인 뒤 다시 보내세요. 남겨 둔 도구 결과에는 해당 assistant 도구 호출도 함께 남겨야 합니다. + 그 밖의 항목은 무시되지 않고 HTTP 400으로 거부됩니다. `think`, `keep_alive`, `suffix`, `raw`, `template`, `context`, 기타 `options`, JSON 스키마 `format`, 이미지 URL이 여기에 해당합니다. Ollama의 모델 관리 및 임베딩 엔드포인트는 제공되지 않습니다. diff --git a/docs-site/src/content/docs/ko/guides/sidecars.md b/docs-site/src/content/docs/ko/guides/sidecars.md index 1ad25a749b3..ebe3a6ca27f 100644 --- a/docs-site/src/content/docs/ko/guides/sidecars.md +++ b/docs-site/src/content/docs/ko/guides/sidecars.md @@ -18,6 +18,12 @@ ChatGPT 로그인(`forward`) 프로바이더나 저장된 Anthropic OAuth 프로 ## 웹 검색 사이드카 +라우팅된 Responses 요청에서는 Anthropic 검색 모델 호출과 연결 재설정 후 재시도도 요청 전체의 +모델 전송·토큰 한도에 포함됩니다. RunTurn 어댑터와 패스스루 검색 브리지에도 적용되며, +`maxSearchesPerTurn`을 높여도 모델 전송 허용량이 추가되지는 않습니다. 한도가 소진되면 +Anthropic에 요청을 보내지 않고 해당 검색을 오류 결과로 반환합니다. 검색 보조 요청은 +원래 모델의 마지막 복구용 예산을 소모하지 않습니다. 독립 `/v1/alpha/search`는 기존 동작을 유지합니다. + Codex가 패스스루가 아닌 라우팅 모델에 호스팅 `web_search`를 요청하면 opencodex는 다음 순서로 처리합니다. diff --git a/docs-site/src/content/docs/ko/guides/web-dashboard.md b/docs-site/src/content/docs/ko/guides/web-dashboard.md index c420c6147cc..a96306024ca 100644 --- a/docs-site/src/content/docs/ko/guides/web-dashboard.md +++ b/docs-site/src/content/docs/ko/guides/web-dashboard.md @@ -82,11 +82,19 @@ bun run dev:gui Usage, Dashboard, 공급자 작업 화면·카탈로그, API 키 화면은 읽을 수 있는 기록이 없어도 일부 기록 제외 경고를 표시합니다. 횟수·날짜·사용 순위는 읽을 수 있는 기록만 반영합니다. 이력이 불완전하면 모델의 ‘많이 사용한 순서’ 저장을 거절합니다. 다른 순서를 선택하거나 이력을 복구한 뒤 다시 시도하세요. +### 앱별 사용량 + +Usage는 현재 접근 범위에서 **모든 앱**, **모든 기기**, 최근 30일을 기본으로 엽니다. 페이지가 열려 있는 동안 앱·기기·기간을 좁힐 수 있으며, 다시 열면 기본값으로 돌아갑니다. + +전체 앱을 선택하면 Models와 Providers 다음에 **앱** 내역이 표시됩니다. 선택한 기기와 기간에 대해 앱별 전체 토큰 비중, 토큰 수, API 정가 환산 추정 비용, 요청 수, 사용량이 측정된 요청 수를 보여 줍니다. Codex CLI·데스크톱·IDE는 Codex로 합산됩니다. 클라이언트 정보가 없는 과거 기록은 **미분류**로 표시하며 **알 수 없음**·**기타**와 구분합니다. 앱 하나를 선택하면 이 내역은 숨겨집니다. 프록시가 내역을 제공하지 않으면 사용 불가로, 빈 내역을 제공하면 보고서에 앱 사용량이 없는 것으로 표시합니다. + ### 요청 로그 필터 요청 상세 화면은 일반 프록시 오류 문구 대신 기록된 업스트림 실패 원인을 우선 표시합니다. 물리적 업스트림 전송 수는 유효할 때 항상 표시하며, 회계 정보가 없거나 유효하지 않으면 0이 아니라 **Unavailable**로 표시합니다. **Retries**는 완전히 정산되고 일관된 단일 시도가 기록된 경우에만 첫 전송 이후 추가 전송 횟수로 표시합니다. 여러 시도, 미해결·불일치 회계 또는 손실 있는 attempt 정규화에서는 재시도 횟수를 만들어 내지 않고 숨깁니다. 알 수 없는 영속 원인은 상태 기반 제목으로 돌아갑니다. **Upstream reason**은 분류된 공급자 상태(속도 제한, 할당량 또는 크레딧 소진, 컨텍스트 창 초과, 지원되지 않는 위치, 계정 인증 필요, 모델 사용 불가 등)를, **Failure detail**은 정지 시간 초과나 바이트 상한처럼 프록시가 작성한 텍스트를 표시합니다. 새 행에는 공급자 자체의 오류 텍스트를 저장하지 않습니다. 이전 `usage.jsonl` 파일에는 남아 있을 수 있지만, 대시보드는 읽을 때 이를 버립니다. -Logs에서는 클라이언트 종류, 가로챈 요청, 공급자, 정확한 모델명, 상태, 시간, +Logs는 앱별 탭 대신 모든 앱의 요청을 함께 표시하고 각 요청에 아이콘만 있는 클라이언트 칩을 붙입니다. 인식된 클라이언트는 SVG 로고를, 알 수 없거나 기타로 분류된 클라이언트는 중립적인 물음표 아이콘을 사용합니다. 앱 이름은 화면에 텍스트로 표시하지 않으며 스크린 리더에는 계속 제공됩니다. 클라이언트 정보가 없다고 Codex나 요청 실패로 간주하지 않습니다. 칩은 공급자·프로토콜과 별개로 클라이언트 앱을 나타내며 앱 필터가 아닙니다. + +Logs에서는 가로챈 요청, 공급자, 정확한 모델명, 상태, 시간, 속도, 대화 ID 조건을 함께 적용할 수 있습니다. 현재 불러온 로그만 필터링하며, 공급자·모델 선택지에는 폴백 시도도 포함됩니다. 모델명은 대소문자와 앞뒤 공백을 무시하지만 부분 이름은 일치하지 않습니다. 선택한 공급자나 모델이 불러온 로그에서 @@ -100,9 +108,9 @@ Logs에서는 클라이언트 종류, 가로챈 요청, 공급자, 정확한 모 없는 요청은 제외됩니다. 성공은 2xx, 오류는 4xx·5xx입니다. 필터를 적용하면 일치하는 건수와 불러온 전체 건수가 표시됩니다. 필터를 초기화하면 -불러온 모든 행이 다시 나타나고 키보드 포커스는 클라이언트 종류의 전체 선택으로 돌아갑니다. +불러온 모든 행이 다시 나타납니다. 조건에 맞는 요청이 없는 상태와 로그 자체가 빈 -상태는 구분해서 표시합니다. 클라이언트 종류는 방향키와 Home/End로 선택할 수 있습니다. +상태는 구분해서 표시합니다. 불러온 범위 밖의 과거 로그는 조회하지 않습니다. ### 섹션으로 바로 가기 diff --git a/docs-site/src/content/docs/ko/reference/cli/providers-accounts.md b/docs-site/src/content/docs/ko/reference/cli/providers-accounts.md index e88c51adf6d..baed5fbfe5d 100644 --- a/docs-site/src/content/docs/ko/reference/cli/providers-accounts.md +++ b/docs-site/src/content/docs/ko/reference/cli/providers-accounts.md @@ -213,7 +213,7 @@ Luna 메타데이터임을 표시해 사용합니다. 목록에 보인다는 사 실행 중인 프록시를 통해 제공자 계정과 API 키 풀을 나열하고 전환합니다. 제공되는 도움말 표면은 다음과 같습니다: ```text -Usage: ocx account ... +Usage: ocx account ... list [provider] Codex account pool, OAuth accounts and API keys (identifiers shown masked as the API returns them). history openai [--limit <1-200>] Recent routing decisions for one Codex pool account. @@ -227,6 +227,7 @@ pause Hold an account out of automatic selection. resume Return a paused account to automatic selection. pause-exhausted Pause every account whose quota is spent. clear-cooldown Drop a cooldown the proxy set after an upstream failure. +reset-affinity [--json] Confirm all session bindings are cleared, even with no accounts; selection and pause state stay unchanged. Run on the Home; a Remote Link Child names the Home and sends no request. strategy [] Pool placement strategy; omit the value to read it. sticky [<1-100>] Requests a bound thread keeps on one account; omit the value to read it. priority [first|earlier|normal|later|last|-100..100|reset] Selection order; omit the value to read it. @@ -241,7 +242,7 @@ main Manage the Codex App logi Codex pool selection applies to the next request after clearing existing affinity; in-flight requests keep their captured account. ``` -하위 명령은 프록시가 실행 중이어야 하며 기록된 런타임 포트를 자동으로 찾습니다. 다만 `import-orca`는 예외로, 미리보기는 로컬 전용이고 `import-orca --apply`는 프록시가 멈춰 있어야 합니다. 성공한 작업은 종료 코드 0으로 끝납니다. 잘못된 사용, 알 수 없는 제공자 또는 계정/키 id, 도달할 수 없는 프록시, API 실패는 종료 코드 1로 끝납니다. 자격 증명 필드는 관리 API가 반환한 그대로 표시됩니다(마스킹도 그대로 포함됩니다). 원시 API 키와 OAuth 토큰은 절대 반환하지 않습니다. 표시 편의 기능은 대시보드와 마찬가지로 클라이언트 쪽에서 합성합니다. `main`은 `openai` 계정 풀의 Codex App 로그인에 대한 CLI 별칭이고, 이메일이 없는 OAuth 계정은 `Account N`으로 표시되며, plan/label 열은 plan, 마스킹된 이메일, label, 마스킹된 키 순으로 대체합니다. +하위 명령은 프록시가 실행 중이어야 하며 기록된 런타임 포트를 자동으로 찾습니다. 다만 `import-orca`는 예외로, 미리보기는 로컬 전용이고 `import-orca --apply`는 프록시가 멈춰 있어야 합니다. 성공한 작업은 종료 코드 0으로 끝납니다. 잘못된 사용, 알 수 없는 제공자 또는 계정/키 id, 도달할 수 없는 프록시, API 실패는 기본적으로 종료 코드 1로 끝납니다. 단, `reset-affinity`는 HTTP 404에 종료 코드 4, HTTP 409에 종료 코드 5를 반환하며, 그 밖의 API 실패나 확인되지 않은 결과는 종료 코드 1을 반환합니다. 자격 증명 필드는 관리 API가 반환한 그대로 표시됩니다(마스킹도 그대로 포함됩니다). 원시 API 키와 OAuth 토큰은 절대 반환하지 않습니다. 표시 편의 기능은 대시보드와 마찬가지로 클라이언트 쪽에서 합성합니다. `main`은 `openai` 계정 풀의 Codex App 로그인에 대한 CLI 별칭이고, 이메일이 없는 OAuth 계정은 `Account N`으로 표시되며, plan/label 열은 plan, 마스킹된 이메일, label, 마스킹된 키 순으로 대체합니다. `--json` 계정 행은 다음 공통 형태를 사용합니다(사용할 수 없는 필드는 생략됩니다): diff --git a/docs-site/src/content/docs/ko/reference/management-api.md b/docs-site/src/content/docs/ko/reference/management-api.md index ae9420f8118..af4e90631d3 100644 --- a/docs-site/src/content/docs/ko/reference/management-api.md +++ b/docs-site/src/content/docs/ko/reference/management-api.md @@ -255,16 +255,19 @@ Aside 프로필 변경은 이때도 한 가지를 저장합니다. 확인을 보 | `GET /api/oauth/status` | 하나의 provider OAuth 흐름을 조회합니다 | 400 알 수 없는 provider | | `POST /api/oauth/logout` | 선택된 provider 자격 증명을 제거합니다 | 400 알 수 없는 provider; `oauth_mutation_busy` | | `GET, DELETE /api/oauth/accounts` | 마스킹된 계정을 나열하거나 계정 하나를 제거합니다 Kiro 행에는 자동 선택 가능 여부인 `autoSelectable`과 제외 시 닫힌 집합의 `skipReason`이 포함됩니다. 활성 단일 계정은 여전히 요청을 보낼 수 있고 quota 조회는 선택 사항입니다. | 400 잘못된 provider/id; 404 계정 없음; `oauth_mutation_busy` | -| `PUT /api/oauth/accounts/active` | 활성 OAuth 계정을 선택합니다 | 400 잘못된 provider/account; `oauth_mutation_busy` | +| `PUT /api/oauth/accounts/active` | 활성 OAuth 계정을 선택합니다 | 400 잘못된 provider/account; `oauth_mutation_busy`; 503 `affinity_reset_not_persisted` (Anthropic: `selectionCommitted: true`, `retryable: true`; 계정 선택은 저장됨; 이미 활성 상태여도 동일 요청을 재전송) | | `GET, PUT, PATCH /api/pool/settings` | 모든 pool 종류(codex, anthropic, generic)의 policy를 읽거나 업데이트합니다. 세 종류 모두 같은 키로 응답하고, 해당 종류가 실제로 적용하는 필드는 `supported`에 나옵니다 | 400 알 수 없는 provider, 해당 종류가 지원하지 않는 필드, 잘못된 값 | | `GET, PUT, PATCH /api/oauth/accounts/pool` | Anthropic과 일반 OAuth provider의 기존 pool policy입니다. `/api/pool/settings`로 대체되었고 기존 클라이언트를 위해 유지합니다 | 400 codex 또는 API 키 provider, 잘못된 policy | | `POST /api/oauth/accounts/clear-cooldown` | OAuth 계정 하나의 런타임 cooldown을 지웁니다 | 400 잘못된 provider/account | | `PUT /api/oauth/accounts/alias` | OAuth 계정 alias를 설정하거나 지웁니다 | 400 잘못된 provider/account/alias | -| `PUT /api/oauth/accounts/pause` | Anthropic 또는 일반 OAuth 계정을 정지·재개합니다. Body `{ provider, accountId, paused }`. 활성 계정을 정지하면 사용 가능한 다른 계정이 있을 때 전환합니다. | 400 지원하지 않는 provider 또는 잘못된 body; 404 계정 없음; `oauth_mutation_busy` | +| `PUT /api/oauth/accounts/pause` | Anthropic 또는 일반 OAuth 계정을 정지·재개합니다. Body `{ provider, accountId, paused }`. 활성 계정을 정지하면 사용 가능한 다른 계정이 있을 때 전환합니다. | 400 지원하지 않는 provider 또는 잘못된 body; 404 계정 없음; `oauth_mutation_busy`; 503 `affinity_reset_not_persisted` (Anthropic: `pauseCommitted: true`, `retryable: true`; 일시정지 상태는 저장됨; 계정과 정지 상태를 유지하고 `retryAffinityReset: true`를 추가해 재시도하면 서버 재시작 후에도 초기화를 확인) | | `GET, POST, DELETE /api/providers/keys` | 마스킹된 provider key를 나열, 추가/활성화, 또는 제거합니다 | 400 잘못된 입력; 404 provider/key 없음 | | `PUT /api/providers/keys/active` | provider의 활성 key를 선택합니다 | 400 잘못된 입력; 404 provider/key 없음 | | `PUT /api/providers/keys/alias` | provider-key alias를 설정하거나 지웁니다 | 400 잘못된 입력; 404 provider/key 없음 | | `GET, POST, PATCH, DELETE /api/keys` | 데이터 평면 admission key를 나열, 생성, 수정, 또는 삭제합니다 | 400 잘못된 본문/id; 404 key 없음 | +| `PUT /api/oauth/accounts/affinity-reset` | 계정 없이 provider 전체의 대화 연결 초기화 저장을 확인합니다. Body `{ provider }`; 성공 시 `affinityResetConfirmed: true`. CLI: `ocx account reset-affinity anthropic --json` (`anthropic2` supported). | 400 invalid body/provider; 503 `affinity_reset_not_persisted`, `retryable: true` | + +대시보드는 저장된 계정이 사라져도 초기화 저장이 확인되기 전에는 경고를 보존합니다. 목록 로드 실패나 활성 계정의 일시정지·재인증 상태에서도 안내와 재시도를 유지합니다. 로드된 목록에서 계정 부재가 확인되면 재시도는 `{ provider }`로 `PUT /api/oauth/accounts/affinity-reset`을 호출하고, `affinityResetConfirmed: true`를 받아야 경고를 지웁니다. 이 작업은 최초 실패 이후 생성된 연결까지 모두 지우지만 계정을 선택·재개하지 않으며 빈 목록에서도 가능합니다. 초기화가 남은 상태의 계정 삭제는 `retryAffinityReset=true`를 보내고, 게시 실패 시 503 `affinity_reset_not_persisted`, `deletionCommitted: true`, `retryable: true`를 반환합니다. 계정은 이미 삭제됐으므로 삭제 대신 초기화 전용 API를 재시도하세요. 일반 삭제는 남은 계정의 연결을 보존합니다. 초기화 전용 정리는 등록된 Anthropic OAuth 인스턴스만 지원하며 기존 관리 인증·출처 검사를 유지합니다. 자격 증명 목록 응답은 의도적으로 마스킹됩니다. OAuth access token과 완전한 provider API key는 대시보드 클라이언트에 반환되지 않습니다. diff --git a/docs-site/src/content/docs/reference/adapters.md b/docs-site/src/content/docs/reference/adapters.md index 6a9251504cb..5eafc00c9a9 100644 --- a/docs-site/src/content/docs/reference/adapters.md +++ b/docs-site/src/content/docs/reference/adapters.md @@ -89,6 +89,18 @@ transport; it does not infer subscription attribution from the inbound protocol. Adjacent text blocks share inline-tag parsing; typed thinking resets it. Unsupported or malformed array blocks return an upstream error. Other hosts keep their existing behavior. This does not add Mistral signature replay. +- Canonical key-auth Mistral Chat (`https://api.mistral.ai/v1`) maps `max_completion_tokens` to + `max_tokens` while keeping an explicit non-null `max_tokens`, and drops `user`, `store`, `thinking`, + `include_reasoning`, `reasoning` and `top_k`. Its preset folds developer messages to system by + default; an explicit `foldDeveloperRoleToSystem: false` overrides this. Native Chat also follows + the configured model effort ladder: an unsupported `medium` becomes `low` for the bundled + DeepSeek/GLM ladders. Mistral Large 4 keeps its supported `medium` rung. +- On that canonical destination, native Chat responses normalize typed arrays in both SSE deltas + and JSON messages: text becomes string `content`, thinking becomes `reasoning_content`. + OpenAI-compatible clients receive strings rather than rendering array objects as `[object Object]`. +- Schema-validation 4xx errors retain a bounded list of recognized field names and error types in + Logs failure details. Upstream messages, submitted values and unknown location keys are omitted; + the client-facing error message policy is unchanged. - Suppresses bare `` text when it duplicates a structured call, and collapses two immediately adjacent identical blocks when exactly one structured call agrees with their function and input. A doubled `input` is reduced to one copy, joined either directly or by one newline, @@ -764,7 +776,10 @@ configuration that names the old id is rewritten at startup. This supports continuity but does not guarantee an upstream cache hit or a particular saving. - Reasoning continuity carries provider signatures across turns. If Cognition refuses a signed Anthropic replay before visible output, Devin retries once with the signature withheld and the - thinking text preserved. + thinking text preserved. Responses clients keep live text/tool streaming: a late turn signature + receives its own output index after the owning item closes, avoiding duplicate text and + unfinished-tool errors in index-keyed clients. Replay pairs the trailer only with adjacent + assistant output; matching IDs are checked when present, while ID-stripping clients remain supported. - Live model discovery via `GetCascadeModelConfigs`; the static seed is filtered against the account's live roster so models not on the plan drop out instead of failing at request time. - Tool definitions are encoded in the request and tool-call events are decoded from the response diff --git a/docs-site/src/content/docs/reference/cli/messaging.md b/docs-site/src/content/docs/reference/cli/messaging.md index 45bb05ce008..bbaaa3175da 100644 --- a/docs-site/src/content/docs/reference/cli/messaging.md +++ b/docs-site/src/content/docs/reference/cli/messaging.md @@ -58,8 +58,10 @@ own state in the selected home; this feature creates no OpenCodex store. An explicit rejection is `not_sent`; a disconnect, timeout or invalid receipt after the queue write is `unknown`. Never replay an uncertain send. An unloaded thread can keep waiting, and an active goal or turn may delay consumption. The -host does not wake an unloaded thread or read the resulting answer. Verify those -behaviors in your installed app/CLI combination before relying on delivery. +host does not wake an unloaded thread. The default queue tools do not read the +resulting answer; see [Opt-in stored turn replies](#opt-in-stored-turn-replies) +for explicit answer reading. Verify delivery behavior in your installed app/CLI +combination before relying on it. One host allows at most four simultaneous helper calls. Each operation has a 20-second budget including executable validation, with a 12-second RPC deadline @@ -72,7 +74,64 @@ peer text is context, never proof of user approval or delegated authority. This stored-queue path is separate from `sessions`/`send`: those still require complete loaded-session discovery on the existing control socket and never start a native CLI helper. Remote peers, Claude inbox transport, persistent handoffs, -answer readback, steering and automatic client configuration are outside this host. +automatic reply delivery, steering and automatic client configuration are outside this host. + +## Opt-in stored turn replies + +Add `--allow-reply-read` to the MCP host's existing arguments to expose a third +tool, `codex_read_reply`. Without this flag the existing two-tool surface and +metadata-only discovery stay unchanged. This option explicitly permits conversation +text to reach the MCP client; local-user access to the selected home is not +per-project/thread authorization. A client message ID is correlation data, not a +credential or proof of user permission. + +```sh +ocx message mcp --codex /absolute/path/to/codex --codex-home /absolute/path/to/.codex --allow-reply-read +``` + +Call `codex_read_reply` with the exact `threadId` and the `clientMessageId` from a +queue receipt. Each call searches at most one page of 50 stored items. A matched +input identifies a turn, and the tool returns one page of that turn's explicit +`final_answer` agent texts. It omits user bodies, reasoning, commands, citations +and tool outputs. The entire call has a shared 20-second budget across up to +three sequential helpers, plus owned cleanup. Each helper retains the existing +12-second RPC deadline and 1 MiB output cap. Text is limited to 16 KiB per result +page, with valid UTF-8 and an explicit per-item `truncated` flag. `nextCursor` +continues items, not omitted bytes of a truncated item. + +Results distinguish these observations: + +- `observed`: the input was matched. `replies` can still be empty. The association + is always `same_turn_not_exclusive`: the result contains final texts from the + same turn, not a certified answer solely to that input. Text can precede a later + input in a shared turn. `turnStatus` is observed from the newest 50 turn summaries, + or `unverified` when outside that window; a completed turn does not complete a goal. +- `not_observed`: no matching input in this search page. This does not mean + non-delivery, deletion or no response. `reachedHistoryEnd` describes this search + position, not delivery or processing. Continue a non-null `nextCursor` to search older items. +- `ambiguous`: multiple matching inputs occurred in the search page. No text is returned. +- `available: false`: disabled access, invalid input/cursor, malformed history, + an unavailable/unsupported native method, cancellation or another read failure. + Native diagnostics are not exposed and failures are not empty successes. + +Pass `nextCursor` back as `cursor` to continue a search or matched-turn page. +After a match, `refreshCursor` rescans that turn from its beginning, allowing +an item that was partial earlier to appear when finalized later. Revisit all +pages when refreshing long turns. Keep/update item IDs in the caller to avoid +duplicates; the host adds no persistent inbox or background polling. A null +`nextCursor` ends only the current history snapshot, not future output. + +Cursors are bounded, caller-owned position data bound to both request IDs. They +are not signed permissions. A matched-turn continuation re-reads the user item to +check its ID, turn and client correlation before projecting turn text. Stale, +malformed or cross-scope positions fail closed rather than restarting all history. +The native methods are `thread/items/list`, `thread/items/read` and +`thread/turns/list`, with no resume, steer, turn start or queue mutation. + +This opt-in contract was observed with Codex CLI 0.166.0. Older builds or legacy +stores can be unavailable; there is no unbounded full-history fallback. These +reads do not prove recipient liveness, goal completion or Claude Desktop registration, +and do not add automatic reply delivery or a reverse Claude transport. ## Discover and send diff --git a/docs-site/src/content/docs/reference/cli/providers-accounts.md b/docs-site/src/content/docs/reference/cli/providers-accounts.md index edba232756a..4f854acd9d2 100644 --- a/docs-site/src/content/docs/reference/cli/providers-accounts.md +++ b/docs-site/src/content/docs/reference/cli/providers-accounts.md @@ -589,7 +589,7 @@ List and switch provider accounts and API-key pools through the running proxy. T surface is: ```text -Usage: ocx account ... +Usage: ocx account ... list [provider] Codex account pool, OAuth accounts and API keys (identifiers shown masked as the API returns them). history openai [--limit <1-200>] Recent routing decisions for one Codex pool account. @@ -603,6 +603,7 @@ pause Hold an account out of automatic selection. resume Return a paused account to automatic selection. pause-exhausted Pause every account whose quota is spent. clear-cooldown Drop a cooldown the proxy set after an upstream failure. +reset-affinity [--json] Confirm all session bindings are cleared, even with no accounts; selection and pause state stay unchanged. Run on the Home; a Remote Link Child names the Home and sends no request. strategy [] Pool placement strategy; least-loaded is Kiro-only. sticky [<1-100>] Requests a bound thread keeps on one account; omit the value to read it. priority [first|earlier|normal|later|last|-100..100|reset] Selection order; omit the value to read it. @@ -621,7 +622,7 @@ A selection-order change applies from the next unbound request and never moves a Account subcommands require the proxy to be running and auto-resolve its recorded runtime port, except `import-orca`: preview is local-only, and `import-orca --apply` requires the proxy to be stopped. Successful operations exit 0. Invalid usage, an unknown provider or account/key id, an unreachable -proxy, or an API failure exits 1. Credential fields are displayed exactly as the management API +proxy, or an API failure normally exits 1. For `reset-affinity`, HTTP 404 exits 4 and HTTP 409 exits 5; other API failures and unconfirmed results exit 1. Credential fields are displayed exactly as the management API returns them (including its masking); raw API keys and OAuth tokens are never returned. Display conveniences are synthesized client-side, same as the dashboard: `main` is the CLI alias for the Codex App login in the `openai` account pool, OAuth accounts without an email appear as `Account N`, diff --git a/docs-site/src/content/docs/reference/configuration/server.md b/docs-site/src/content/docs/reference/configuration/server.md index 6309066f19e..7c435e2b08e 100644 --- a/docs-site/src/content/docs/reference/configuration/server.md +++ b/docs-site/src/content/docs/reference/configuration/server.md @@ -92,10 +92,12 @@ Use `GET /api/native-reasoning-retention` to read the resolved booleans and `PUT OpenCodex bounds how much one task may fan out. A task is the root conversation, identified by the client's `x-codex-parent-thread-id` header, together with every subagent it starts. Three count ceilings apply to each task, and requests over any of them are refused locally with HTTP 429 and an `x-opencodex-local-refusal` header naming the ceiling (`workflow_concurrency_exhausted`, `workflow_sends_exhausted` or `workflow_children_exhausted`); no provider is contacted. +If physical admission refuses a request after token reservation, its unstarted reservation is returned immediately. Started sends and other pending bookings remain accounted for. Native continuation refusals retain HTTP 429 in the request log as well as in the client error. + | Field | Default | Allowed | Meaning | | --- | --- | --- | --- | | `maxConcurrentChildren` | `8` | 2–64 | Turns one task may run at the same time. One slot is always reserved for the task's own conversation, so subagents may use one fewer. | -| `maxPhysicalSends` | `256` | 1–10000 | Upstream sends, retries included, after which the task's new requests are refused for the rest of the 10-minute window. The count is checked when a request is admitted, so retries inside a request that was already admitted, and requests admitted concurrently, can take the total past the ceiling. | +| `maxPhysicalSends` | `256` | 1–10000 | Upstream send admissions, retries included, per 10-minute window. Every physical admission checks and claims a slot atomically, so concurrent requests and retries share the same ceiling. An unused dispatch permit consumes no slot; a claimed physical start remains counted even if it later fails. | | `maxDistinctChildren` | `64` | 1–1024 | Distinct subagent threads one task may start inside the same window. | The defaults suit most installs. Raise them when a workflow that legitimately runs many subagents in parallel keeps hitting a ceiling; lower them to stop a runaway fan-out sooner: @@ -111,6 +113,8 @@ The defaults suit most installs. Raise them when a workflow that legitimately ru The **Subagents** page of the dashboard shows the effective ceilings and edits them. `GET /api/workflow-budget/settings` returns the effective values with their defaults and allowed ranges; `PUT` with a partial object updates them, and JSON `null` restores the defaults. Dashboard/API changes apply to the next request without a restart and never reset what a task has already used; `POST /api/workflow-budget/clear` remains the way to forgive one task. A malformed file block is ignored with a warning and the built-in ceilings stay in force. Local `ocx config set/unset` edits require a restart to converge the running proxy. The 10-minute window, the reserved interactive slot and the 512 tracked tasks are not configurable. +An idle task keeps its tracking slot while it has unexpired sends or child history, even if it has only partly used a limit. The proxy reuses only slots with no active turn, no remaining count history and no exhausted token budget. If all 512 slots must be retained, a new task receives local HTTP 429 with `workflow_tracking_exhausted` instead of resetting another task's allowance. Slots become reusable as their count windows expire; durable token ceilings remain independent. Count history is process-local and is not made persistent by this retention rule. + ## Upstream transport recovery While the canonical ChatGPT upstream WebSocket waits for the first Responses event after diff --git a/docs-site/src/content/docs/reference/management-api.md b/docs-site/src/content/docs/reference/management-api.md index 0bd240258a0..c8dd980ae47 100644 --- a/docs-site/src/content/docs/reference/management-api.md +++ b/docs-site/src/content/docs/reference/management-api.md @@ -654,8 +654,8 @@ Labels are read on login/import or refresh, never inferred from quota percentage | `POST /api/oauth/logout` | Remove the selected provider credential | 400 unknown provider; `oauth_mutation_busy` | | `GET /api/oauth/accounts` | List masked accounts; Anthropic and generic OAuth account rows include their `paused` state. Kiro rows include `autoSelectable` and a closed `skipReason` when excluded from automatic selection; an active singleton may still send. Quota remains opt-in. | 400 invalid provider | | `DELETE /api/oauth/accounts` | Remove one account | 400 invalid provider/id; 404 account missing; `oauth_mutation_busy` | -| `PUT /api/oauth/accounts/active` | Select the active OAuth account | 400 invalid provider/account; 404 account missing; 409 account paused; `oauth_mutation_busy` | -| `PUT /api/oauth/accounts/pause` | Pause or resume one Anthropic or generic OAuth account. Body `{ provider, accountId, paused }`; pausing the active account selects the next usable account when available. Pause is durable and independent of pool enablement; resume preserves health and credentials. | 400 unsupported provider or invalid body; 404 account missing; `oauth_mutation_busy` | +| `PUT /api/oauth/accounts/active` | Select the active OAuth account | 400 invalid provider/account; 404 account missing; 409 account paused; `oauth_mutation_busy`; 503 `affinity_reset_not_persisted` (Anthropic: `selectionCommitted: true`, `retryable: true`; account change saved; resend the same request even if already active) | +| `PUT /api/oauth/accounts/pause` | Pause or resume one Anthropic or generic OAuth account. Body `{ provider, accountId, paused }`; pausing the active account selects the next usable account when available. Pause is durable and independent of pool enablement; resume preserves health and credentials. | 400 unsupported provider or invalid body; 404 account missing; `oauth_mutation_busy`; 503 `affinity_reset_not_persisted` (Anthropic: `pauseCommitted: true`, `retryable: true`; pause state saved; preserve the account and pause state, add `retryAffinityReset: true`, and retry to confirm the reset even after a server restart) | | `GET, PUT, PATCH /api/pool/settings` | Read or update pool policy for any kind (codex, anthropic, generic); answers with the same keys for all three and declares in `supported` which the kind honours | 400 unknown provider, a field the kind does not support, or an invalid value | | `GET, PUT, PATCH /api/oauth/accounts/pool` | Legacy per-pool policy for Anthropic and generic OAuth providers; superseded by `/api/pool/settings` and kept for existing clients | 400 codex or api-key provider, or invalid policy | | `POST /api/oauth/accounts/clear-cooldown` | Clear one OAuth account's runtime cooldown | 400 invalid provider/account | @@ -664,6 +664,9 @@ Labels are read on login/import or refresh, never inferred from quota percentage | `PUT /api/providers/keys/active` | Select a provider's active key | 400 invalid input; 404 provider/key missing | | `PUT /api/providers/keys/alias` | Set or clear a provider-key alias | 400 invalid input; 404 provider/key missing | | `GET, POST, PATCH, DELETE /api/keys` | List, create, edit, or delete data-plane admission keys | 400 invalid body/id; 404 key missing | +| `PUT /api/oauth/accounts/affinity-reset` | Confirm a provider-wide session-affinity reset without requiring an account. Body `{ provider }`; success includes `affinityResetConfirmed: true`. CLI: `ocx account reset-affinity anthropic --json` (`anthropic2` supported). | 400 invalid body/provider; 503 `affinity_reset_not_persisted`, `retryable: true` | + +The dashboard retains an unconfirmed affinity-reset warning even when the saved account disappears. Loading errors and paused or reauthentication-blocked active targets keep their retry with feedback. Once a loaded list confirms absence, Retry uses `PUT /api/oauth/accounts/affinity-reset` with `{ provider }`; it clears the warning only after `affinityResetConfirmed: true`. This provider-wide operation clears all bindings, including those created since the first failure, without selecting or resuming an account, and also works with an empty roster. Deleting an account with an outstanding reset sends `retryAffinityReset=true`; a failed publication returns 503 `affinity_reset_not_persisted`, `deletionCommitted: true` and `retryable: true`. The account is already deleted: retry the reset-only endpoint, not deletion. Plain deletion preserves surviving-account affinity. Reset-only cleanup is limited to the registered Anthropic OAuth instances and retains the ordinary management authentication/origin checks. `POST /api/oauth/login/cancel` can also dismiss the selected settled failed login attempt. It leaves stored credentials and successful login results intact; status then reports the remaining flow or idle. Cancelling an in-progress attempt still records its cancelled outcome. diff --git a/docs-site/src/content/docs/reference/proxy-formats.md b/docs-site/src/content/docs/reference/proxy-formats.md index b17aaec2094..4786c484b00 100644 --- a/docs-site/src/content/docs/reference/proxy-formats.md +++ b/docs-site/src/content/docs/reference/proxy-formats.md @@ -50,6 +50,18 @@ snapshot omits it. Auto/none, incomplete results and unknown contracts retain th ## Required tools on translated Chat routes +Before sending, the proxy rejects a mandatory tool choice that the selected Chat destination cannot +represent, such as `computer_use` or a required allowed-tool list containing MCP selections. A direct +route returns HTTP 400 without an upstream request. A routing profile can continue to another +originally eligible destination under its configured fallback policy, preserving the original choice. +An explicit nonempty catalog with `required` is also refused when no tool survives in the full parsed Chat catalog, including tools supplied through input items. A surviving client tool keeps a mixed catalog eligible; hosted web search retains its later sidecar handling. +If no eligible target remains and all attempted candidates were locally incompatible, the profile returns the same HTTP 400 diagnostic; +the Responses API returns HTTP 503 for an exhausted combo, naming the mandatory-tool constraint. Other failures +retain their existing error handling. Copilot Auto uses the final negotiated model and wire for this check. +A local incompatibility before inference does not count as a model send, including after a session refresh. +Negotiating a replacement account or model does not rewrite the attribution of an earlier physical send. +If a send-budget limit stops traversal first, the proxy retains the preceding failure instead of blaming tool compatibility. + A translated OpenAI Chat response must contain a complete selected client tool call when `tool_choice` is required, names a tool, or uses a required allowed-tool selection. If the upstream finishes normally without one, the turn fails with `required_tool_not_called` and preserves usage. diff --git a/docs-site/src/content/docs/ru/guides/ollama-clients.md b/docs-site/src/content/docs/ru/guides/ollama-clients.md index 0bef2a6a995..7fde2f37832 100644 --- a/docs-site/src/content/docs/ru/guides/ollama-clients.md +++ b/docs-site/src/content/docs/ru/guides/ollama-clients.md @@ -43,6 +43,11 @@ curl "$OLLAMA_HOST/api/tags" - `options`: `temperature`, `top_p`, `num_predict` и `stop`. - `format`: только `"json"`. +Каждый запрос `/api/chat` допускает **не более 1 024 вызовов инструментов суммарно** во всех сообщениях +assistant, включая вызовы, для которых уже есть результаты. При превышении лимита запрос возвращает +HTTP 400 до отправки провайдеру. Сократите историю диалога и отправьте её снова, сохраняя каждый +оставленный результат инструмента вместе с соответствующим вызовом в сообщении assistant. + Всё остальное отклоняется с HTTP 400, а не игнорируется: `think`, `keep_alive`, `suffix`, `raw`, `template`, `context`, прочие `options`, `format` в виде JSON-схемы и URL изображений. Конечные точки Ollama для управления моделями и эмбеддингов не предоставляются. diff --git a/docs-site/src/content/docs/ru/guides/web-dashboard.md b/docs-site/src/content/docs/ru/guides/web-dashboard.md index 7740d84b535..284f65a5ecd 100644 --- a/docs-site/src/content/docs/ru/guides/web-dashboard.md +++ b/docs-site/src/content/docs/ru/guides/web-dashboard.md @@ -72,15 +72,23 @@ bun run dev:gui Страницы использования, дашборда, провайдеров, каталога провайдеров и API-ключей предупреждают об исключённых записях, даже если читаемых строк нет. Счётчики, даты и рейтинги основаны только на читаемых записях. Сохранение порядка моделей по частоте использования отклоняется при неполной истории: выберите другой порядок или восстановите историю перед повтором. +### Использование по приложениям + +Usage открывается с выбранными значениями **Все приложения**, **Все устройства** и **30 дней** в пределах текущего доступа. Пока страница открыта, можно выбрать приложение, устройство или период; при повторном открытии возвращаются значения по умолчанию. + +При выборе всех приложений после Models и Providers появляется раздел **Приложения**. Для выбранных устройств и периода он показывает долю приложения в общем числе токенов, число токенов, оценочную стоимость по прайс-листу API, число запросов и запросов с измеренным использованием. Codex CLI, настольное приложение и IDE объединены в Codex. Старые записи без сведений о клиенте попадают в **Без классификации**, отдельно от **Неизвестно** и **Другое**. При выборе одного приложения разбивка скрывается. Если прокси не предоставляет разбивку, она помечается как недоступная; пустая разбивка означает отсутствие использования приложений в отчёте. + ### Фильтрация запросов В деталях запроса распознанная причина сбоя upstream имеет приоритет над общим текстом об ошибке прокси. Число физических отправок всегда показывается, если оно корректно; при отсутствующем или некорректном учёте отображается **Unavailable**, а не ноль. **Retries** считает отправки после первой только при наличии одной попытки с полностью урегулированным и согласованным учётом. При нескольких попытках, незавершённом или противоречивом учёте и при нормализации с потерями retries не выводятся, вместо того чтобы выдумывать их. Неизвестные сохранённые причины возвращают заголовок по статусу без сырой атрибуции. **Upstream reason** показывает классифицированное состояние провайдера (например, превышен лимит запросов, исчерпаны квота или кредиты, превышено окно контекста, регион не поддерживается, требуется подтверждение аккаунта или модель недоступна), а **Failure detail** — текст, сформированный прокси, например о тайм-ауте простоя или лимите байтов; новые строки не сохраняют собственный текст ошибки провайдера. Он может оставаться в старых файлах `usage.jsonl`, но панель отбрасывает его при чтении. -Фильтры объединяют источник, перехваченные запросы, провайдера, точную модель, статус, время, скорость и ID диалога в загруженном журнале. Варианты включают резервные попытки; модель сравнивается без учёта регистра и крайних пробелов, но не по подстроке. Исчезнувший вариант сбрасывается на все записи. +Logs показывает запросы всех приложений вместе, со значком клиента без текста у каждого запроса вместо вкладок приложений. Для распознанных клиентов используются их SVG-логотипы, а для неизвестных и прочих — нейтральный значок вопросительного знака. Названия приложений остаются доступны программам экранного доступа без видимого текста. Отсутствие сведений о клиенте не означает Codex или ошибку запроса. Значок обозначает клиентское приложение независимо от провайдера и протокола и не служит фильтром приложений. + +Фильтры объединяют перехваченные запросы, провайдера, точную модель, статус, время, скорость и ID диалога в загруженном журнале. Варианты включают резервные попытки; модель сравнивается без учёта регистра и крайних пробелов, но не по подстроке. Исчезнувший вариант сбрасывается на все записи. Периоды 15 минут, час и сутки обновляются каждые 30 секунд на вкладке Logs даже при выключенном автообновлении. Скорость — выходные токены в секунду за полную длительность запроса: меньше 15, от 15 до менее 50, не менее 50; недоступные значения исключаются при активном фильтре скорости. Успех — 2xx, ошибки — 4xx/5xx. -Счётчик показывает совпадения из загруженного общего числа; сброс возвращает все строки. Нет совпадений и пустой журнал различаются. Источник выбирается стрелками и Home/End. История вне загруженного журнала не запрашивается. +Счётчик показывает совпадения из загруженного общего числа; сброс возвращает все строки. Нет совпадений и пустой журнал различаются. История вне загруженного журнала не запрашивается. ### Ссылки на разделы diff --git a/docs-site/src/content/docs/ru/reference/cli/providers-accounts.md b/docs-site/src/content/docs/ru/reference/cli/providers-accounts.md index 584eacdb910..8aeaa9cccd8 100644 --- a/docs-site/src/content/docs/ru/reference/cli/providers-accounts.md +++ b/docs-site/src/content/docs/ru/reference/cli/providers-accounts.md @@ -118,7 +118,7 @@ ocx login anthropic Поставляемая help-surface выглядит так: ```text -Usage: ocx account ... +Usage: ocx account ... list [provider] Codex account pool, OAuth accounts and API keys (identifiers shown masked as the API returns them). history openai [--limit <1-200>] Recent routing decisions for one Codex pool account. @@ -132,6 +132,7 @@ pause Hold an account out of automatic selection. resume Return a paused account to automatic selection. pause-exhausted Pause every account whose quota is spent. clear-cooldown Drop a cooldown the proxy set after an upstream failure. +reset-affinity [--json] Confirm all session bindings are cleared, even with no accounts; selection and pause state stay unchanged. Run on the Home; a Remote Link Child names the Home and sends no request. strategy [] Pool placement strategy; omit the value to read it. sticky [<1-100>] Requests a bound thread keeps on one account; omit the value to read it. priority [first|earlier|normal|later|last|-100..100|reset] Selection order; omit the value to read it. @@ -148,7 +149,7 @@ Codex pool selection applies to the next request after clearing existing affinit Подкоманды требуют запущенного прокси и сами определяют записанный runtime-port, кроме `import-orca`: предпросмотр работает только локально, а `import-orca --apply` требует остановленного прокси. Успешные операции завершаются с кодом 0. Некорректное использование, неизвестный провайдер, account/key id, -недостижимый прокси или ошибка API приводят к коду 1. Поля credential'ов выводятся ровно в том +недостижимый прокси или ошибка API обычно приводят к коду 1. Для `reset-affinity` HTTP 404 означает код выхода 4, HTTP 409 — код 5; остальные ошибки API и неподтверждённые результаты дают код 1. Поля credential'ов выводятся ровно в том виде, как их возвращает management API (включая его masking); сырые API-key'и и OAuth-token'ы никогда не возвращаются. Display convenience синтезируется на стороне клиента, как и в дашборде: `main` — это alias CLI для логина Codex App внутри пула `openai`, OAuth-аккаунты без email diff --git a/docs-site/src/content/docs/ru/reference/management-api.md b/docs-site/src/content/docs/ru/reference/management-api.md index 3d59f47db89..2006ad30eb1 100644 --- a/docs-site/src/content/docs/ru/reference/management-api.md +++ b/docs-site/src/content/docs/ru/reference/management-api.md @@ -271,16 +271,19 @@ Endpoint'ы storage cleanup могут перемещать или навсег | `GET /api/oauth/status` | Опрашивать OAuth-flow одного провайдера | 400 unknown provider | | `POST /api/oauth/logout` | Удалить сохранённый credential выбранного провайдера | 400 unknown provider; `oauth_mutation_busy` | | `GET, DELETE /api/oauth/accounts` | Показать список masked-аккаунтов или удалить один аккаунт Строки Kiro содержат `autoSelectable` и закрытый `skipReason` при исключении из автоматического выбора; единственный активный аккаунт всё ещё может отправлять запросы. Чтение квоты остаётся необязательным. | 400 invalid provider/id; 404 account missing; `oauth_mutation_busy` | -| `PUT /api/oauth/accounts/active` | Выбрать активный OAuth-аккаунт | 400 invalid provider/account; `oauth_mutation_busy` | +| `PUT /api/oauth/accounts/active` | Выбрать активный OAuth-аккаунт | 400 invalid provider/account; `oauth_mutation_busy`; 503 `affinity_reset_not_persisted` (Anthropic: `selectionCommitted: true`, `retryable: true`; выбор сохранён; повторите тот же запрос, даже если аккаунт уже активен) | | `GET, PUT, PATCH /api/pool/settings` | Прочитать или обновить policy пула любого вида (codex, anthropic, generic); все три отвечают одинаковыми ключами, а поля, которые вид действительно применяет, перечислены в `supported` | 400 неизвестный provider, поле, которое вид не поддерживает, или недопустимое значение | | `GET, PUT, PATCH /api/oauth/accounts/pool` | Прежняя policy пула для Anthropic и обычных OAuth-провайдеров; заменена на `/api/pool/settings` и сохранена для существующих клиентов | 400 codex или api-key provider, либо недопустимая policy | | `POST /api/oauth/accounts/clear-cooldown` | Очистить runtime cooldown одного OAuth-аккаунта | 400 invalid provider/account | | `PUT /api/oauth/accounts/alias` | Задать или очистить alias OAuth-аккаунта | 400 invalid provider/account/alias | -| `PUT /api/oauth/accounts/pause` | Приостановить/возобновить Anthropic или обычный OAuth-аккаунт. Body `{ provider, accountId, paused }`; при паузе активного аккаунта выбирается другой пригодный аккаунт, если он есть. | 400 неподдерживаемый provider или неверный body; 404 аккаунт не найден; `oauth_mutation_busy` | +| `PUT /api/oauth/accounts/pause` | Приостановить/возобновить Anthropic или обычный OAuth-аккаунт. Body `{ provider, accountId, paused }`; при паузе активного аккаунта выбирается другой пригодный аккаунт, если он есть. | 400 неподдерживаемый provider или неверный body; 404 аккаунт не найден; `oauth_mutation_busy`; 503 `affinity_reset_not_persisted` (Anthropic: `pauseCommitted: true`, `retryable: true`; состояние паузы сохранено; сохраните тот же аккаунт и состояние, добавьте `retryAffinityReset: true` и повторите запрос, в том числе после перезапуска сервера) | | `GET, POST, DELETE /api/providers/keys` | Показать список masked provider-key'ов, добавить/активировать один или удалить один | 400 invalid input; 404 provider/key missing | | `PUT /api/providers/keys/active` | Выбрать активный ключ провайдера | 400 invalid input; 404 provider/key missing | | `PUT /api/providers/keys/alias` | Задать или очистить alias provider-key'а | 400 invalid input; 404 provider/key missing | | `GET, POST, PATCH, DELETE /api/keys` | Показать список, создать, отредактировать или удалить admission key data plane | 400 invalid body/id; 404 key missing | +| `PUT /api/oauth/accounts/affinity-reset` | Подтвердить запись сброса привязок разговоров provider без обязательного аккаунта. Body `{ provider }`; успех: `affinityResetConfirmed: true`. CLI: `ocx account reset-affinity anthropic --json` (`anthropic2` supported). | 400 invalid body/provider; 503 `affinity_reset_not_persisted`, `retryable: true` | + +Панель сохраняет предупреждение до подтверждения записи сброса, даже если аккаунт исчез. Ошибка загрузки, пауза или необходимость повторного входа не удаляют попытку. После подтверждения отсутствия аккаунта повтор вызывает `PUT /api/oauth/accounts/affinity-reset` с `{ provider }` и снимает предупреждение только при `affinityResetConfirmed: true`. Операция очищает все привязки, включая созданные после первого сбоя, не выбирает и не возобновляет аккаунты и работает при пустом списке. Удаление с незавершённым сбросом передаёт `retryAffinityReset=true`; ошибка записи возвращает 503 `affinity_reset_not_persisted`, `deletionCommitted: true` и `retryable: true`. Аккаунт уже удалён: повторяйте только сброс. Обычное удаление сохраняет привязки остальных аккаунтов. Сброс доступен только зарегистрированным Anthropic OAuth instances и сохраняет проверки управляющей аутентификации и источника. Ответы со списками credential'ов намеренно маскируются. OAuth access-token'ы и полные API-key'и провайдеров клиентам дашборда не возвращаются. diff --git a/docs-site/src/content/docs/tr/guides/ollama-clients.md b/docs-site/src/content/docs/tr/guides/ollama-clients.md index 6921e7b67b7..8409b46a1fd 100644 --- a/docs-site/src/content/docs/tr/guides/ollama-clients.md +++ b/docs-site/src/content/docs/tr/guides/ollama-clients.md @@ -43,6 +43,11 @@ Diğer tüm `/api/*` yolları opencodex yönetim API'sinin parçası olarak kal - `options`: `temperature`, `top_p`, `num_predict` ve `stop`. - `format`: yalnızca `"json"`. +Her `/api/chat` isteği, tüm assistant mesajlarında **toplam en fazla 1.024 araç çağrısı** kabul eder; +sonucu zaten bulunan çağrılar da bu sayıya dahildir. Sınır aşılırsa istek sağlayıcıya ulaşmadan HTTP 400 +döndürür. Konuşma geçmişini kısaltıp yeniden gönderin; tuttuğunuz her araç sonucuyla birlikte ilgili +assistant araç çağrısını da koruyun. + Bunların dışındaki her şey yok sayılmak yerine HTTP 400 ile reddedilir: `think`, `keep_alive`, `suffix`, `raw`, `template`, `context`, diğer `options`, JSON şeması biçimindeki `format` ve görsel URL'leri. Ollama'nın model yönetimi ve embedding uç noktaları sağlanmaz. diff --git a/docs-site/src/content/docs/tr/guides/web-dashboard.md b/docs-site/src/content/docs/tr/guides/web-dashboard.md index 2166d73c290..e366cd8fa5f 100644 --- a/docs-site/src/content/docs/tr/guides/web-dashboard.md +++ b/docs-site/src/content/docs/tr/guides/web-dashboard.md @@ -94,15 +94,23 @@ zaman yukarı akışta yenilemeye zorlamaz. Kullanım, panel, sağlayıcı çalışma alanı, sağlayıcı kataloğu ve API anahtarı görünümleri, okunabilir kayıt kalmasa bile dışlanan kayıtlar için uyarı gösterir. Sayılar, tarihler ve kullanım sıralamaları yalnızca okunabilir kayıtlara dayanır. Geçmiş eksikse en çok kullanılan model sırası kaydedilmez; başka bir sıra seçin veya yeniden denemeden önce geçmişi onarın. +### Uygulamaya göre kullanım + +Usage, geçerli erişim kapsamı içinde **Tüm uygulamalar**, **Tüm cihazlar** ve **30 gün** seçili olarak açılır. Sayfa açıkken uygulama, cihaz veya zaman aralığını daraltabilirsiniz; yeniden açıldığında varsayılanlar geri gelir. + +Tüm uygulamalar seçiliyken Models ve Providers bölümlerinden sonra **Uygulamalar** dökümü görünür. Seçilen cihazlar ve dönem için uygulama başına toplam jetonlardaki payı, jeton sayısını, tahmini API liste fiyatı maliyetini, istek sayısını ve kullanımı ölçülen istek sayısını gösterir. Codex CLI, masaüstü ve IDE kullanımı Codex altında birleştirilir. İstemci bilgisi olmayan eski kayıtlar **Sınıflandırılmamış** olarak gösterilir; **Bilinmeyen** ve **Diğer** kategorilerinden ayrıdır. Tek bir uygulama seçildiğinde döküm gizlenir. Dökümü sağlamayan proxy için kullanılamadığı belirtilir; boş döküm, raporda uygulama kullanımı olmadığı anlamına gelir. + ### İstek günlüklerini filtreleme İstek ayrıntıları, genel proxy hatası metni yerine kaydedilmiş upstream hata nedenini öncelikli gösterir. Fiziksel gönderim sayısı geçerliyse her zaman gösterilir; eksik veya geçersiz muhasebe bilgisi sıfır yerine **Unavailable** olarak görüntülenir. **Retries**, yalnızca muhasebesi tamamen kesinleşmiş ve tutarlı tek bir deneme kaydı bulunduğunda ilk gönderimden sonraki ek gönderim sayısını gösterir. Birden fazla deneme, çözümlenmemiş veya tutarsız muhasebe ve kayıplı attempt normalizasyonu, uydurma yeniden deneme sayısı yerine retries alanını gizler. Bilinmeyen kalıcı nedenler, ham atıf olmadan durum tabanlı başlığa döner. **Upstream reason**, sınıflandırılmış bir sağlayıcı durumunu (örneğin hız sınırı, kota veya kredinin tükenmesi, bağlam penceresinin aşılması, desteklenmeyen konum, hesap doğrulaması gerekmesi veya modelin kullanılamaması), **Failure detail** ise takılma zaman aşımı veya bayt sınırı gibi proxy tarafından yazılan metni gösterir; yeni satırlar sağlayıcının kendi hata metnini saklamaz. Eski `usage.jsonl` dosyalarında hâlâ bulunabilir, ancak pano okurken bu metni atar. -Filtreler yüklü günlükte yüzey, yakalanan istekler, sağlayıcı, tam model adı, durum, zaman, hız ve konuşma kimliğini birleştirir. Seçenekler yedek denemeleri de içerir; model eşleşmesi büyük/küçük harfi ve dış boşlukları yok sayar, kısmi adları eşleştirmez. Kaybolan seçenek tüm kayıtlara döner. +Logs, uygulama sekmeleri yerine tüm uygulamaların isteklerini birlikte gösterir ve her isteğe yalnızca simge içeren bir istemci rozeti ekler. Tanınan istemciler SVG logolarını, bilinmeyen veya diğer istemciler ise nötr bir soru işareti simgesini kullanır. Uygulama adları görünür metin olmadan ekran okuyuculara sunulmaya devam eder. İstemci bilgisinin olmaması, Codex veya istek hatası anlamına gelmez. Rozet, sağlayıcı ve protokolden bağımsız olarak istemci uygulamasını belirtir; uygulama filtresi değildir. + +Filtreler yüklü günlükte yakalanan istekler, sağlayıcı, tam model adı, durum, zaman, hız ve konuşma kimliğini birleştirir. Seçenekler yedek denemeleri de içerir; model eşleşmesi büyük/küçük harfi ve dış boşlukları yok sayar, kısmi adları eşleştirmez. Kaybolan seçenek tüm kayıtlara döner. Son 15 dakika, saat ve gün pencereleri Logs sekmesinde otomatik yenileme kapalıyken de 30 saniyede bir güncellenir. Hız, tam istek süresindeki saniyelik çıktı jetonudur: 15 altı, 15 dahil 50 altı, en az 50; hız filtresi açıkken ölçülemeyenler dışlanır. Başarı 2xx, hata 4xx/5xx anlamındadır. -Sayaç eşleşen ve yüklü toplam sayıları gösterir; sıfırlama tüm satırları geri getirir. Eşleşme olmaması boş günlükten ayrılır. Yüzey seçimi oklar ve Home/End ile çalışır. Yüklü günlüğün dışındaki geçmiş sorgulanmaz. +Sayaç eşleşen ve yüklü toplam sayıları gösterir; sıfırlama tüm satırları geri getirir. Eşleşme olmaması boş günlükten ayrılır. Yüklü günlüğün dışındaki geçmiş sorgulanmaz. ### Bir bölüme bağlantı verme diff --git a/docs-site/src/content/docs/tr/reference/cli/messaging.md b/docs-site/src/content/docs/tr/reference/cli/messaging.md index e88dea990d1..126675b548b 100644 --- a/docs-site/src/content/docs/tr/reference/cli/messaging.md +++ b/docs-site/src/content/docs/tr/reference/cli/messaging.md @@ -15,6 +15,11 @@ stdio app-server başlatır. Kayıtlı konuşmaların canlı olduğu doğrulanma yanıtı işlenmiş veya yanıtlanmış anlamına gelmez. Kurulum ve sınırlar için [İngilizce MCP başvurusuna](/reference/cli/messaging/#mcp-queue-host) bakın. +`--allow-reply-read` seçeneği ayrıca `codex_read_reply` aracını açar ve kayıtlı +konuşmanın son yanıt metnini MCP istemcisine aktarabilir. Varsayılan olarak kapalıdır. +Aynı turda bulunmak tek bir girdiye özel yanıt sahipliği anlamına gelmez. +[Ayrıntılar ve sınırlar](/reference/cli/messaging/#opt-in-stored-turn-replies). + ## Keşfetme ve gönderme ```bash diff --git a/docs-site/src/content/docs/tr/reference/cli/providers-accounts.md b/docs-site/src/content/docs/tr/reference/cli/providers-accounts.md index f0cec9d17ac..1b91857e4ea 100644 --- a/docs-site/src/content/docs/tr/reference/cli/providers-accounts.md +++ b/docs-site/src/content/docs/tr/reference/cli/providers-accounts.md @@ -137,7 +137,7 @@ Bir sağlayıcı için saklanan OAuth kimlik bilgisini kaldırın. listeleyin ve değiştirin. Sağlanan yardım arayüzü şöyledir: ```text -Usage: ocx account ... +Usage: ocx account ... list [provider] Codex account pool, OAuth accounts and API keys (identifiers shown masked as the API returns them). history openai [--limit <1-200>] Recent routing decisions for one Codex pool account. @@ -151,6 +151,7 @@ pause Hold an account out of automatic selection. resume Return a paused account to automatic selection. pause-exhausted Pause every account whose quota is spent. clear-cooldown Drop a cooldown the proxy set after an upstream failure. +reset-affinity [--json] Confirm all session bindings are cleared, even with no accounts; selection and pause state stay unchanged. Run on the Home; a Remote Link Child names the Home and sends no request. strategy [] Havuz stratejisi; least-loaded yalnızca Kiro içindir. sticky [<1-100>] Requests a bound thread keeps on one account; omit the value to read it. priority [first|earlier|normal|later|last|-100..100|reset] Selection order; omit the value to read it. @@ -168,7 +169,7 @@ A selection-order change applies from the next unbound request and never moves a `import-orca` dışındaki alt komutlar proxy'nin çalışmasını gerektirir ve kaydedilen çalışma zamanı portunu otomatik çözer; `import-orca` önizlemesi tamamen yereldir ve `import-orca --apply` proxy'nin durdurulmuş olmasını gerektirir. Başarılı işlemler 0 ile çıkar. Geçersiz kullanım, bilinmeyen bir sağlayıcı veya hesap/anahtar kimliği, erişilemeyen bir proxy veya -bir API hatası 1 ile çıkar. Kimlik bilgisi alanları tam olarak yönetim API'sinin +bir API hatası normalde 1 ile çıkar. `reset-affinity` için HTTP 404 çıkış kodu 4, HTTP 409 çıkış kodu 5 döndürür; diğer API hataları ve doğrulanmamış sonuçlar 1 döndürür. Kimlik bilgisi alanları tam olarak yönetim API'sinin döndürdüğü gibi (maskelemesi dahil) görüntülenir; ham API anahtarları ve OAuth belirteçleri asla döndürülmez. Görüntüleme kolaylıkları kontrol paneli gibi istemci tarafında sentezlenir: `main`, `openai` hesap havuzundaki Codex App diff --git a/docs-site/src/content/docs/tr/reference/management-api.md b/docs-site/src/content/docs/tr/reference/management-api.md index 33824ff6c6c..65d1b758543 100644 --- a/docs-site/src/content/docs/tr/reference/management-api.md +++ b/docs-site/src/content/docs/tr/reference/management-api.md @@ -296,15 +296,18 @@ Güvenilir ilk model listesi hazır olana kadar `/api/selected-models` ve `/api/ | `GET /api/oauth/status` | Bir sağlayıcının OAuth akışını yoklayın | 400 bilinmeyen sağlayıcı | | `POST /api/oauth/logout` | Seçilen sağlayıcı kimlik bilgisini kaldırın | 400 bilinmeyen sağlayıcı; `oauth_mutation_busy` | | `GET, DELETE /api/oauth/accounts` | Maskelenmiş hesapları listeleyin veya bir hesabı kaldırın Kiro satırları otomatik seçimden dışlandığında `autoSelectable` ve kapalı bir `skipReason` taşır; tek etkin hesap yine istek gönderebilir. Kota isteğe bağlıdır. | 400 geçersiz sağlayıcı/kimlik; 404 hesap eksik; `oauth_mutation_busy` | -| `PUT /api/oauth/accounts/active` | Aktif OAuth hesabını seçin | 400 geçersiz sağlayıcı/hesap; `oauth_mutation_busy` | +| `PUT /api/oauth/accounts/active` | Aktif OAuth hesabını seçin | 400 geçersiz sağlayıcı/hesap; `oauth_mutation_busy`; 503 `affinity_reset_not_persisted` (Anthropic: `selectionCommitted: true`, `retryable: true`; hesap seçimi kaydedildi; hesap etkin olsa bile aynı isteği yeniden gönderin) | | `GET, PUT, PATCH /api/oauth/accounts/pool` | Anthropic OAuth havuz politikasını okuyun veya güncelleyin | 400 Anthropic olmayan sağlayıcı veya geçersiz politika | | `POST /api/oauth/accounts/clear-cooldown` | Bir OAuth hesabının çalışma zamanı soğuma süresini temizleyin | 400 geçersiz sağlayıcı/hesap | | `PUT /api/oauth/accounts/alias` | Bir OAuth hesap takma adını ayarlayın veya temizleyin | 400 geçersiz sağlayıcı/hesap/takma ad | -| `PUT /api/oauth/accounts/pause` | Anthropic veya genel OAuth hesabını duraklatın/sürdürün. Body `{ provider, accountId, paused }`; etkin hesap duraklatıldığında varsa kullanılabilir başka hesaba geçilir. | 400 desteklenmeyen sağlayıcı veya geçersiz body; 404 hesap yok; `oauth_mutation_busy` | +| `PUT /api/oauth/accounts/pause` | Anthropic veya genel OAuth hesabını duraklatın/sürdürün. Body `{ provider, accountId, paused }`; etkin hesap duraklatıldığında varsa kullanılabilir başka hesaba geçilir. | 400 desteklenmeyen sağlayıcı veya geçersiz body; 404 hesap yok; `oauth_mutation_busy`; 503 `affinity_reset_not_persisted` (Anthropic: `pauseCommitted: true`, `retryable: true`; duraklatma durumu kaydedildi; hesabı ve durumu koruyup `retryAffinityReset: true` ekleyerek yeniden deneyin; sunucu yeniden başlatıldıktan sonra da sıfırlama doğrulanır) | | `GET, POST, DELETE /api/providers/keys` | Maskelenmiş sağlayıcı anahtarlarını listeleyin, bir tane ekleyin/etkinleştirin veya kaldırın | 400 geçersiz girdi; 404 sağlayıcı/anahtar eksik | | `PUT /api/providers/keys/active` | Bir sağlayıcının etkin anahtarını seçin | 400 geçersiz girdi; 404 sağlayıcı/anahtar eksik | | `PUT /api/providers/keys/alias` | Bir sağlayıcı anahtarı takma adını ayarlayın veya temizleyin | 400 geçersiz girdi; 404 sağlayıcı/anahtar eksik | | `GET, POST, PATCH, DELETE /api/keys` | Veri düzlemi kabul anahtarlarını listeleyin, oluşturun, düzenleyin veya silin | 400 geçersiz gövde/kimlik; 404 anahtar eksik | +| `PUT /api/oauth/accounts/affinity-reset` | Hesap gerektirmeden provider konuşma bağlantılarının sıfırlanmasını doğrulayın. Body `{ provider }`; başarı: `affinityResetConfirmed: true`. CLI: `ocx account reset-affinity anthropic --json` (`anthropic2` supported). | 400 invalid body/provider; 503 `affinity_reset_not_persisted`, `retryable: true` | + +Pano, hesap kaybolsa bile sıfırlamanın kaydedildiği doğrulanana kadar uyarıyı korur. Yükleme hatası, duraklatma veya yeniden giriş gereksinimi denemeyi silmez. Hesabın yokluğu listeden doğrulandığında Yeniden Dene, `{ provider }` ile `PUT /api/oauth/accounts/affinity-reset` çağırır; uyarı yalnızca `affinityResetConfirmed: true` ile kaldırılır. Bu işlem ilk hatadan sonra oluşanlar dahil tüm bağlantıları temizler, hesap seçmez veya devam ettirmez ve boş listede de çalışır. Bekleyen sıfırlama varken silme `retryAffinityReset=true` gönderir; kayıt hatası 503 `affinity_reset_not_persisted`, `deletionCommitted: true` ve `retryable: true` döndürür. Hesap zaten silinmiştir: silmeyi değil yalnızca sıfırlamayı yeniden deneyin. Normal silme kalan hesapların bağlantılarını korur. İşlem kayıtlı Anthropic OAuth örnekleriyle sınırlıdır ve mevcut yönetim kimlik doğrulaması ile kaynak kontrollerini korur. Kimlik bilgisi listesi yanıtları kasıtlı olarak maskelenir. OAuth erişim belirteçleri ve eksiksiz sağlayıcı API anahtarları kontrol paneli istemcilerine diff --git a/docs-site/src/content/docs/zh-cn/guides/ollama-clients.md b/docs-site/src/content/docs/zh-cn/guides/ollama-clients.md index 2701bc4d13d..14999cb4126 100644 --- a/docs-site/src/content/docs/zh-cn/guides/ollama-clients.md +++ b/docs-site/src/content/docs/zh-cn/guides/ollama-clients.md @@ -42,6 +42,10 @@ curl "$OLLAMA_HOST/api/tags" - `options`:`temperature`、`top_p`、`num_predict` 和 `stop`。 - `format`:仅支持 `"json"`。 +每个 `/api/chat` 请求中,所有 assistant 消息的**工具调用总数最多为 1,024 次**,已返回结果的调用也计入总数。 +超过上限时,请求会在发送给提供商之前返回 HTTP 400。请缩短对话历史后重试,并将保留的工具结果与对应的 +assistant 工具调用一起保留。 + 其他内容会以 HTTP 400 拒绝,而不是被忽略,包括 `think`、`keep_alive`、`suffix`、`raw`、 `template`、`context`、其他 `options`、JSON Schema 形式的 `format` 以及图片 URL。不提供 Ollama 的模型管理和 嵌入端点。 diff --git a/docs-site/src/content/docs/zh-cn/guides/web-dashboard.md b/docs-site/src/content/docs/zh-cn/guides/web-dashboard.md index 95ce0954b41..9767c159b8d 100644 --- a/docs-site/src/content/docs/zh-cn/guides/web-dashboard.md +++ b/docs-site/src/content/docs/zh-cn/guides/web-dashboard.md @@ -69,15 +69,23 @@ bun run dev:gui 用量、仪表板、供应商工作区、供应商目录和 API 密钥页面会提示部分记录被排除,即使没有可读取的记录。次数、日期和使用排名仅反映可读取的记录。历史不完整时,无法保存模型的最常用排序;请选择其他排序或修复历史后重试。 +### 按应用查看用量 + +Usage 默认在当前访问范围内选择**所有应用**、**所有设备**和**30 天**。页面打开期间可缩小应用、设备或时间范围;重新打开时恢复默认值。 + +选择所有应用时,Models 和 Providers 后会显示**应用**明细,列出所选设备和时间范围内各应用占总 token 的比例、token 数、按 API 标价估算的费用、请求数及已测量用量的请求数。Codex CLI、桌面端和 IDE 合并为 Codex。没有客户端归属信息的历史记录显示为**未分类**,与**未知**和**其他**区分。选择单个应用会隐藏此明细。代理未提供明细时显示不可用;空明细表示报告中没有应用用量。 + ### 筛选请求日志 请求详情优先显示已记录的上游失败原因,而不是通用的代理错误文案。有效的物理上游发送次数始终显示;缺失或无效的会计信息显示为 **Unavailable**,而不是 0。只有在记录到一次已完全结算且一致的尝试时,**Retries** 才显示首次发送后的额外发送次数。多次尝试、未解决或不一致的会计信息,以及有损失的 attempt 归一化,都不会编造重试次数。未知的持久化原因回退为基于状态的标题。**Upstream reason** 显示经过分类的提供方状态(例如速率受限、配额或额度耗尽、超出上下文窗口、所在地区不受支持、需要验证账户、模型不可用),**Failure detail** 显示由代理撰写的文本,例如停滞超时或字节上限;新记录不会保存提供方自身的错误文本。较旧的 `usage.jsonl` 文件中可能仍有此类文本,但仪表盘读取时会将其丢弃。 -Logs 可组合界面、被拦截请求、提供商、完整模型名、状态、时间、速度和会话 ID,筛选当前已加载的日志。选项包含回退尝试;模型匹配忽略大小写及首尾空格,但不做部分匹配。日志中消失的选项恢复为全部。 +Logs 将所有应用的请求放在一起,并为每个请求显示仅含图标的客户端标记,取代应用标签页。已识别的客户端使用其 SVG 标志,未知或其他客户端使用中性的问号图标。应用名称不以可见文本显示,但仍可供屏幕阅读器读取。缺少客户端归属信息不表示 Codex 或请求失败。标记用于标识客户端应用,与提供商和协议无关,也不是应用筛选器。 + +Logs 可组合被拦截请求、提供商、完整模型名、状态、时间、速度和会话 ID,筛选当前已加载的日志。选项包含回退尝试;模型匹配忽略大小写及首尾空格,但不做部分匹配。日志中消失的选项恢复为全部。 时间范围为最近 15 分钟、1 小时或 1 天;Logs 标签页每 30 秒更新一次,即使关闭自动刷新也会更新。速度按完整请求耗时计算每秒输出 token,分为小于 15、15 至小于 50、至少 50;启用速度筛选时排除无测量值的请求。成功为 2xx,错误为 4xx/5xx。 -显示匹配数与已加载总数;重置恢复全部行,并区分无匹配与空日志。界面选择支持方向键及 Home/End,不查询已加载范围之外的历史记录。 +显示匹配数与已加载总数;重置恢复全部行,并区分无匹配与空日志。不查询已加载范围之外的历史记录。 ### 链接到某个部分 diff --git a/docs-site/src/content/docs/zh-cn/reference/cli/providers-accounts.md b/docs-site/src/content/docs/zh-cn/reference/cli/providers-accounts.md index cf8e96e1320..63ddac2fa64 100644 --- a/docs-site/src/content/docs/zh-cn/reference/cli/providers-accounts.md +++ b/docs-site/src/content/docs/zh-cn/reference/cli/providers-accounts.md @@ -115,7 +115,7 @@ ocx login anthropic 通过正在运行的代理列出并切换提供方账号和 API 密钥池。随附的帮助输出如下: ```text -Usage: ocx account ... +Usage: ocx account ... list [provider] Codex account pool, OAuth accounts and API keys (identifiers shown masked as the API returns them). history openai [--limit <1-200>] Recent routing decisions for one Codex pool account. @@ -129,6 +129,7 @@ pause Hold an account out of automatic selection. resume Return a paused account to automatic selection. pause-exhausted Pause every account whose quota is spent. clear-cooldown Drop a cooldown the proxy set after an upstream failure. +reset-affinity [--json] Confirm all session bindings are cleared, even with no accounts; selection and pause state stay unchanged. Run on the Home; a Remote Link Child names the Home and sends no request. strategy [] Pool placement strategy; omit the value to read it. sticky [<1-100>] Requests a bound thread keeps on one account; omit the value to read it. priority [first|earlier|normal|later|last|-100..100|reset] Selection order; omit the value to read it. @@ -145,7 +146,7 @@ Codex pool selection applies to the next request after clearing existing affinit 除 `import-orca` 外,子命令都要求代理正在运行,CLI 会自动解析其记录的运行时端口;`import-orca` 的预览完全在本地进行,而 `import-orca --apply` 要求代理已停止。成功操作的 退出码为 0。无效用法、未知的提供方或账号/密钥 id、无法访问的代理,或 API 失败 -都会以 1 退出。凭据字段会严格按管理 API 返回的样子显示(包括其屏蔽格式); +通常以 1 退出。但 `reset-affinity` 遇到 HTTP 404 时退出码为 4,遇到 HTTP 409 时为 5;其他 API 失败或未确认的结果退出码为 1。凭据字段会严格按管理 API 返回的样子显示(包括其屏蔽格式); 原始 API 密钥和 OAuth token 永远不会返回。显示上的便利字段都像仪表盘一样在 客户端侧合成:`main` 是 `openai` 账号池中 Codex App 登录的 CLI 别名,没有邮箱的 OAuth 账号会显示为 `Account N`,而 plan/label 列会在 plan、屏蔽后的邮箱、label 和 diff --git a/docs-site/src/content/docs/zh-cn/reference/management-api.md b/docs-site/src/content/docs/zh-cn/reference/management-api.md index 3679c8c5390..69bb3fe27f8 100644 --- a/docs-site/src/content/docs/zh-cn/reference/management-api.md +++ b/docs-site/src/content/docs/zh-cn/reference/management-api.md @@ -237,15 +237,18 @@ Aside 配置档的变更在这种情况下仍会保存一件事:确认之后 | `GET /api/oauth/status` | 轮询某个 provider 的 OAuth 流程 | 400 provider 未知 | | `POST /api/oauth/logout` | 移除选定的 provider 凭证 | 400 provider 未知;`oauth_mutation_busy` | | `GET, DELETE /api/oauth/accounts` | 列出已脱敏账户或移除一个账户 Kiro 行包含自动选择状态 `autoSelectable`,被排除时还包含封闭集合的 `skipReason`。唯一的活动账户仍可发送请求,配额查询仍为可选。 | 400 provider/id 无效;404 账户缺失;`oauth_mutation_busy` | -| `PUT /api/oauth/accounts/active` | 选择当前活跃的 OAuth 账户 | 400 provider/账户无效;`oauth_mutation_busy` | +| `PUT /api/oauth/accounts/active` | 选择当前活跃的 OAuth 账户 | 400 provider/账户无效;`oauth_mutation_busy`; 503 `affinity_reset_not_persisted` (Anthropic: `selectionCommitted: true`, `retryable: true`; 账户选择已保存;即使已激活,也请重新发送相同请求) | | `GET, PUT, PATCH /api/oauth/accounts/pool` | 读取或更新 Anthropic OAuth 池策略 | 400 非 Anthropic provider 或策略无效 | | `POST /api/oauth/accounts/clear-cooldown` | 清除一个 OAuth 账户的运行时冷却 | 400 provider/账户无效 | | `PUT /api/oauth/accounts/alias` | 设置或清除 OAuth 账户别名 | 400 provider/账户/别名无效 | -| `PUT /api/oauth/accounts/pause` | 暂停或恢复 Anthropic 或通用 OAuth 账户。Body `{ provider, accountId, paused }`;暂停活跃账户时,如有其他可用账户则切换过去。 | 400 不支持的 provider 或无效 body;404 账户不存在;`oauth_mutation_busy` | +| `PUT /api/oauth/accounts/pause` | 暂停或恢复 Anthropic 或通用 OAuth 账户。Body `{ provider, accountId, paused }`;暂停活跃账户时,如有其他可用账户则切换过去。 | 400 不支持的 provider 或无效 body;404 账户不存在;`oauth_mutation_busy`; 503 `affinity_reset_not_persisted` (Anthropic: `pauseCommitted: true`, `retryable: true`; 暂停状态已保存;保留账户和暂停状态,添加 `retryAffinityReset: true` 后重试,服务器重启后也会确认重置) | | `GET, POST, DELETE /api/providers/keys` | 列出已脱敏的 provider 密钥,添加/激活一个,或移除一个 | 400 输入无效;404 provider/密钥缺失 | | `PUT /api/providers/keys/active` | 选择某个 provider 的活跃密钥 | 400 输入无效;404 provider/密钥缺失 | | `PUT /api/providers/keys/alias` | 设置或清除 provider 密钥别名 | 400 输入无效;404 provider/密钥缺失 | | `GET, POST, PATCH, DELETE /api/keys` | 列出、创建、编辑或删除数据平面准入密钥 | 400 请求体/id 无效;404 密钥缺失 | +| `PUT /api/oauth/accounts/affinity-reset` | 无需账户即可确认 provider 全部会话绑定的重置已保存。Body `{ provider }`;成功返回 `affinityResetConfirmed: true`. CLI: `ocx account reset-affinity anthropic --json` (`anthropic2` supported). | 400 invalid body/provider; 503 `affinity_reset_not_persisted`, `retryable: true` | + +即使保存的账户已消失,仪表板也会保留未确认的重置警告。列表加载失败、账户暂停或需要重新认证时,会保留重试并提示。列表确认账户不存在后,重试使用 `{ provider }` 调用 `PUT /api/oauth/accounts/affinity-reset`,只有收到 `affinityResetConfirmed: true` 才清除警告。该操作会清除所有绑定,包括首次失败后创建的绑定,但不会选择或恢复账户,账户列表为空时也可执行。存在待完成重置时,删除请求发送 `retryAffinityReset=true`;保存失败返回 503 `affinity_reset_not_persisted`、`deletionCommitted: true` 和 `retryable: true`。账户已经删除,请重试专用重置接口而非删除。普通删除保留其他账户的绑定。专用清理仅适用于注册的 Anthropic OAuth 实例,并保留原有管理认证和来源检查。 凭证列表响应会刻意脱敏。OAuth 访问令牌和完整的 provider API 密钥不会返回给仪表板客户端。 diff --git a/docs-site/src/content/docs/zh-tw/guides/ollama-clients.md b/docs-site/src/content/docs/zh-tw/guides/ollama-clients.md index fb4cf878e5b..af93baf37cc 100644 --- a/docs-site/src/content/docs/zh-tw/guides/ollama-clients.md +++ b/docs-site/src/content/docs/zh-tw/guides/ollama-clients.md @@ -42,6 +42,10 @@ curl "$OLLAMA_HOST/api/tags" - `options`:`temperature`、`top_p`、`num_predict` 與 `stop`。 - `format`:僅支援 `"json"`。 +每個 `/api/chat` 請求中,所有 assistant 訊息的**工具呼叫總數最多為 1,024 次**,已傳回結果的呼叫也計入總數。 +超過上限時,請求會在傳送給供應商之前回傳 HTTP 400。請縮短對話歷史後重試,並將保留的工具結果與對應的 +assistant 工具呼叫一起保留。 + 其他內容會以 HTTP 400 拒絕,而不是被忽略,包括 `think`、`keep_alive`、`suffix`、`raw`、 `template`、`context`、其他 `options`、JSON Schema 形式的 `format` 以及圖片 URL。不提供 Ollama 的模型管理與 嵌入端點。 diff --git a/docs-site/src/content/docs/zh-tw/guides/web-dashboard.md b/docs-site/src/content/docs/zh-tw/guides/web-dashboard.md index 31e2c50807d..f175c90e21e 100644 --- a/docs-site/src/content/docs/zh-tw/guides/web-dashboard.md +++ b/docs-site/src/content/docs/zh-tw/guides/web-dashboard.md @@ -78,15 +78,23 @@ GUI session 簽發到服務的頁面中,並在到期或代理重啟時靜默 用量、儀表板、供應商工作區、供應商目錄和 API 金鑰頁面會提示部分記錄被排除,即使沒有可讀取的記錄。次數、日期和使用排名僅反映可讀取的記錄。歷史不完整時,無法儲存模型的最常用排序;請選擇其他排序或修復歷史後重試。 +### 依應用程式查看用量 + +Usage 預設在目前存取範圍內選擇**所有應用程式**、**所有裝置**和**30 天**。頁面開啟期間可縮小應用程式、裝置或時間範圍;重新開啟時恢復預設值。 + +選擇所有應用程式時,Models 和 Providers 後會顯示**應用程式**明細,列出所選裝置與時間範圍內各應用程式占總 token 的比例、token 數、依 API 標價估算的費用、請求數及已測量用量的請求數。Codex CLI、桌面版和 IDE 合併為 Codex。沒有用戶端歸屬資訊的歷史記錄顯示為**未分類**,與**未知**和**其他**區分。選擇單一應用程式會隱藏此明細。代理未提供明細時顯示無法使用;空明細表示報告中沒有應用程式用量。 + ### 篩選請求日誌 請求詳細資訊會優先顯示已記錄的上游失敗原因,而不是通用的代理錯誤文案。有效的上游實際傳送次數一律顯示;缺少或無效的記帳資訊則顯示 **Unavailable**,而非 0。只有在記錄到一次已完全結算且一致的嘗試時,**Retries** 才顯示首次傳送後的額外傳送次數。多次嘗試、未解決或不一致的記帳資訊,以及有損失的 attempt 正規化,都不會憑空產生重試次數。未知的持久化原因會回退為依狀態顯示的標題。**Upstream reason** 顯示經過分類的供應商狀態(例如速率受限、配額或額度用盡、超出上下文視窗、所在地區不受支援、需要驗證帳戶、模型無法使用),**Failure detail** 顯示由代理撰寫的文字,例如停滯逾時或位元組上限;新記錄不會儲存供應商本身的錯誤文字。較舊的 `usage.jsonl` 檔案中可能仍有此類文字,但儀表板讀取時會將其捨棄。 -Logs 可組合介面、被攔截請求、供應商、完整模型名稱、狀態、時間、速度和對話 ID,篩選目前已載入的日誌。選項包含回退嘗試;模型比對忽略大小寫及頭尾空白,但不做部分比對。日誌中消失的選項恢復為全部。 +Logs 將所有應用程式的請求放在一起,並為每個請求顯示僅含圖示的用戶端標記,取代應用程式分頁。已識別的用戶端使用其 SVG 標誌,未知或其他用戶端使用中性的問號圖示。應用程式名稱不以可見文字顯示,但仍可供螢幕閱讀器讀取。缺少用戶端歸屬資訊不表示 Codex 或請求失敗。標記用於識別用戶端應用程式,與供應商和通訊協定無關,也不是應用程式篩選器。 + +Logs 可組合被攔截請求、供應商、完整模型名稱、狀態、時間、速度和對話 ID,篩選目前已載入的日誌。選項包含回退嘗試;模型比對忽略大小寫及頭尾空白,但不做部分比對。日誌中消失的選項恢復為全部。 時間範圍為最近 15 分鐘、1 小時或 1 天;Logs 分頁每 30 秒更新一次,即使關閉自動重新整理也會更新。速度按完整請求耗時計算每秒輸出 token,分為小於 15、15 至小於 50、至少 50;啟用速度篩選時排除無測量值的請求。成功為 2xx,錯誤為 4xx/5xx。 -顯示符合數與已載入總數;重設恢復全部列,並區分無符合結果與空日誌。介面選擇支援方向鍵及 Home/End,不查詢已載入範圍以外的歷史記錄。 +顯示符合數與已載入總數;重設恢復全部列,並區分無符合結果與空日誌。不查詢已載入範圍以外的歷史記錄。 ### 連結到某個部分 diff --git a/docs-site/src/content/docs/zh-tw/reference/cli/providers-accounts.md b/docs-site/src/content/docs/zh-tw/reference/cli/providers-accounts.md index 0ec96d57678..1f308227b34 100644 --- a/docs-site/src/content/docs/zh-tw/reference/cli/providers-accounts.md +++ b/docs-site/src/content/docs/zh-tw/reference/cli/providers-accounts.md @@ -86,7 +86,7 @@ ocx login anthropic 透過執行中的代理列出並切換供應商帳號與 API-key 池。隨附的說明介面如下: ```text -Usage: ocx account ... +Usage: ocx account ... list [provider] Codex 帳號池、OAuth 帳號與 API 金鑰(識別碼依 API 回傳遮罩顯示)。 history openai [--limit <1-200>] 單一 Codex 帳號池帳號的近期路由決策。 @@ -101,6 +101,7 @@ pause 將帳號移出自動選擇。 resume 將暫停的帳號放回自動選擇。 pause-exhausted 暫停所有配額已用盡的帳號。 clear-cooldown 清除上游失敗後設定的冷卻。 +reset-affinity [--json] 確認所有工作階段綁定均已清除,即使沒有帳號也適用;帳號選擇與暫停狀態保持不變。請在 Home 執行;Remote Link Child 只會指出 Home,不會傳送請求。 strategy [] 帳號池放置策略;省略取值即讀取目前值。 sticky [<1-100>] 已綁定執行緒在同一帳號上保留的請求數;省略取值即讀取目前值。 remove --yes 在存在檢查後移除已儲存的帳號或金鑰。 @@ -114,7 +115,7 @@ main 管理帳號池稱為 'ma Codex 池選擇套用於清除既有親和性後的下一個請求;進行中的請求保留其擷取的帳號。 ``` -除 `import-orca` 外,子指令都需要代理正在執行,CLI 自動解析其記錄的 runtime 連接埠;`import-orca` 的預覽完全在本機進行,而 `import-orca --apply` 需要代理已停止。成功的操作離開 0。無效用法、未知供應商或帳號/金鑰 id、不可達的代理或 API 失敗則離開 1。憑證欄位完全依管理 API 回傳的方式顯示(包含其遮罩);原始 API 金鑰與 OAuth token 永不回傳。顯示便利性在客戶端合成,與儀表板相同:`main` 是 `openai` 帳號池中 Codex App 登入的 CLI 別名,無電子郵件的 OAuth 帳號顯示為 `Account N`,而 plan/label 欄位在 plan、遮罩電子郵件、label 與遮罩金鑰之間回退。 +除 `import-orca` 外,子指令都需要代理正在執行,CLI 自動解析其記錄的 runtime 連接埠;`import-orca` 的預覽完全在本機進行,而 `import-orca --apply` 需要代理已停止。成功的操作離開 0。無效用法、未知供應商或帳號/金鑰 id、不可達的代理或 API 失敗通常以 1 結束。但 `reset-affinity` 遇到 HTTP 404 時結束碼為 4,遇到 HTTP 409 時為 5;其他 API 失敗或未確認的結果則為 1。憑證欄位完全依管理 API 回傳的方式顯示(包含其遮罩);原始 API 金鑰與 OAuth token 永不回傳。顯示便利性在客戶端合成,與儀表板相同:`main` 是 `openai` 帳號池中 Codex App 登入的 CLI 別名,無電子郵件的 OAuth 帳號顯示為 `Account N`,而 plan/label 欄位在 plan、遮罩電子郵件、label 與遮罩金鑰之間回退。 `--json` 帳號列使用此通用結構(不可用時省略可選欄位): diff --git a/docs-site/src/content/docs/zh-tw/reference/management-api.md b/docs-site/src/content/docs/zh-tw/reference/management-api.md index f4d13b561b4..0c878cda1c8 100644 --- a/docs-site/src/content/docs/zh-tw/reference/management-api.md +++ b/docs-site/src/content/docs/zh-tw/reference/management-api.md @@ -241,8 +241,8 @@ Aside 設定檔的變更在這種情況下仍會儲存一件事:確認之後 | `POST /api/oauth/logout` | 移除所選的供應商憑證 | 400 未知供應商;`oauth_mutation_busy` | | `GET /api/oauth/accounts` | 列出遮罩帳號;Anthropic 與通用 OAuth 帳號列也會提供 `paused` 狀態。Kiro 列包含自動選取狀態 `autoSelectable`,排除時還包含封閉集合的 `skipReason`。唯一的有效帳號仍可傳送請求,配額查詢仍為選用。 | 400 無效供應商 | | `DELETE /api/oauth/accounts` | 移除一個帳號 | 400 無效供應商/id;404 帳號缺失;`oauth_mutation_busy` | -| `PUT /api/oauth/accounts/active` | 選擇現用 OAuth 帳號 | 400 無效供應商/帳號;404 帳號缺失;409 帳號已暫停;`oauth_mutation_busy` | -| `PUT /api/oauth/accounts/pause` | 暫停或恢復一個 Anthropic 或通用 OAuth 帳號。Body `{ provider, accountId, paused }`;若暫停現用帳號,且有可用帳號,會切換至下一個。暫停會持久儲存且不受帳號池開關影響,恢復保留健康狀態與憑證。 | 400 不支援的供應商或無效 body;404 帳號缺失;`oauth_mutation_busy` | +| `PUT /api/oauth/accounts/active` | 選擇現用 OAuth 帳號 | 400 無效供應商/帳號;404 帳號缺失;409 帳號已暫停;`oauth_mutation_busy`; 503 `affinity_reset_not_persisted` (Anthropic: `selectionCommitted: true`, `retryable: true`; 帳戶選擇已儲存;即使已啟用,也請重新傳送相同請求) | +| `PUT /api/oauth/accounts/pause` | 暫停或恢復一個 Anthropic 或通用 OAuth 帳號。Body `{ provider, accountId, paused }`;若暫停現用帳號,且有可用帳號,會切換至下一個。暫停會持久儲存且不受帳號池開關影響,恢復保留健康狀態與憑證。 | 400 不支援的供應商或無效 body;404 帳號缺失;`oauth_mutation_busy`; 503 `affinity_reset_not_persisted` (Anthropic: `pauseCommitted: true`, `retryable: true`; 暫停狀態已儲存;保留帳號和暫停狀態,加入 `retryAffinityReset: true` 後重試,伺服器重新啟動後也會確認重設) | | `GET, PUT, PATCH /api/oauth/accounts/pool` | 讀取或更新 Anthropic OAuth 池政策 | 400 非 Anthropic 供應商或無效政策 | | `POST /api/oauth/accounts/clear-cooldown` | 清除一個 OAuth 帳號的 runtime 冷卻 | 400 無效供應商/帳號 | | `PUT /api/oauth/accounts/alias` | 設定或清除 OAuth 帳號別名 | 400 無效供應商/帳號/別名 | @@ -250,6 +250,9 @@ Aside 設定檔的變更在這種情況下仍會儲存一件事:確認之後 | `PUT /api/providers/keys/active` | 選擇供應商的現用金鑰 | 400 無效輸入;404 供應商/金鑰缺失 | | `PUT /api/providers/keys/alias` | 設定或清除供應商金鑰別名 | 400 無效輸入;404 供應商/金鑰缺失 | | `GET, POST, PATCH, DELETE /api/keys` | 列出、建立、編輯或刪除 data-plane 許可金鑰 | 400 無效 body/id;404 金鑰缺失 | +| `PUT /api/oauth/accounts/affinity-reset` | 無需帳戶即可確認 provider 全部對話繫結的重設已儲存。Body `{ provider }`;成功回傳 `affinityResetConfirmed: true`. CLI: `ocx account reset-affinity anthropic --json` (`anthropic2` supported). | 400 invalid body/provider; 503 `affinity_reset_not_persisted`, `retryable: true` | + +即使儲存的帳戶已消失,儀表板也會保留未確認的重設警告。清單載入失敗、帳戶暫停或需要重新驗證時,會保留重試並提示。清單確認帳戶不存在後,重試使用 `{ provider }` 呼叫 `PUT /api/oauth/accounts/affinity-reset`,只有收到 `affinityResetConfirmed: true` 才清除警告。此操作會清除所有繫結,包括首次失敗後建立的繫結,但不會選擇或恢復帳戶,帳戶清單為空時也可執行。存在待完成重設時,刪除請求傳送 `retryAffinityReset=true`;儲存失敗回傳 503 `affinity_reset_not_persisted`、`deletionCommitted: true` 和 `retryable: true`。帳戶已經刪除,請重試專用重設端點而非刪除。一般刪除保留其他帳戶的繫結。專用清理僅適用於註冊的 Anthropic OAuth 執行個體,並保留原有管理驗證與來源檢查。 憑證清單回應被刻意遮罩。OAuth access token 與完整的供應商 API 金鑰不回傳給儀表板客戶端。 diff --git a/gui/classic/src/pages/Startup.tsx b/gui/classic/src/pages/Startup.tsx index add0b2cc45c..52f9d1ccb60 100644 --- a/gui/classic/src/pages/Startup.tsx +++ b/gui/classic/src/pages/Startup.tsx @@ -5,6 +5,7 @@ import { readSessionListCache, writeSessionListCache } from "../session-list-cac import { Notice } from "../ui"; import { useDataSurface } from "../data-surface"; import { DataSurfaceSkeleton } from "../components/data-surface"; +import { startupPresentation } from "../startup-health-ui"; import { StartupDetailsSection, StartupHeroSection, @@ -249,6 +250,7 @@ export default function Startup({ apiBase, machineApiBase = apiBase, connected = // warm revisits where `data` is already seeded from session cache. const loading = loadState.refreshing; const failed = Boolean(data?.diagnosticStale) || loadState.showError; + const unverified = data !== null && startupPresentation(data, failed) === "unverified"; useEffect(() => { if (!data?.diagnosticStale) return; @@ -346,8 +348,8 @@ export default function Startup({ apiBase, machineApiBase = apiBase, connected = ) : data ? ( <> - {loadState.showError && {t("startup.error")}} - {failed && ( + {loadState.showError && !unverified && {t("startup.error")}} + {failed && !unverified && (
{t("startup.staleData")}
diff --git a/gui/classic/src/pages/Tray.tsx b/gui/classic/src/pages/Tray.tsx index 02399615ce7..d2552296304 100644 --- a/gui/classic/src/pages/Tray.tsx +++ b/gui/classic/src/pages/Tray.tsx @@ -122,7 +122,7 @@ export default function Tray({ resolveApiBase = resolveTrayApiBase }: TrayProps payload.activeCodexAccountId = selection.activeCodexAccountId ?? '__main__'; } catch { /* Missing selection is unknown, never inferred from quota. */ } } - return { name: source.name, switchKind: source.switchKind, accounts: parseAccounts(payload) }; + return { name: source.name, switchKind: source.switchKind, accounts: parseAccounts(payload, source.switchKind, index => t('pws.accountOrdinal', { count: index })) }; } catch { return { name: source.name, switchKind: source.switchKind, accounts: [], unavailable: true }; } })); @@ -187,7 +187,7 @@ export default function Tray({ resolveApiBase = resolveTrayApiBase }: TrayProps window.removeEventListener('blur', blur); window.removeEventListener('focus', focus); window.removeEventListener('opencodex:tray-visibility', nativeVisibility); }; - }, [revision, apiBase]); + }, [revision, apiBase, t]); const number = (value: unknown) => finite(value) ? formatTokens(value, locale) : '—'; const totals = (raw: TrayTotals | undefined) => { diff --git a/gui/classic/src/pages/dashboard-overview-head.tsx b/gui/classic/src/pages/dashboard-overview-head.tsx index c956ca1f4b9..664b809d20d 100644 --- a/gui/classic/src/pages/dashboard-overview-head.tsx +++ b/gui/classic/src/pages/dashboard-overview-head.tsx @@ -94,10 +94,11 @@ export function DashboardOverviewHead({
{startupHealth ? (
{data.desktop && (
-
{t("startup.protection.desktop")}{t(!failed && data.desktop.viable ? "startup.desktopHint" : startupRiskDetailKey(data))}
+
{t("startup.protection.desktop")}{!failed && data.desktop.viable ? {t("startup.desktopHint")} : !failed && data.desktop.running ? {t("startup.desktopRunning")} : null}
- + {startupPresentation(data, failed) === "unverified" ? {t("startup.loginUnverifiedTitle")} : }
)} @@ -261,9 +263,8 @@ export function StartupRecoverySection({ The one-click install/repair buttons above are the primary path; the copyable commands are the fallback. Open by default only while protection is missing. */} -
+
{t(desktopManagesStartup(data) ? "startup.protection.desktop" : "startup.recoveryHint")} - {desktopManagesStartup(data) &&

{t(startupRiskDetailKey(data))}

}
{data.serviceSupported && !desktopManagesStartup(data) && (
diff --git a/gui/design-system/components.md b/gui/design-system/components.md index fd70d60cbd6..b7243a6aa17 100644 --- a/gui/design-system/components.md +++ b/gui/design-system/components.md @@ -64,6 +64,8 @@ - 버튼형: `ui.tsx`의 `Switch`, `.switch`, `.knob` - label/input형: `.toggle`, `.slider` +`Switch`의 이진 상태는 `role="switch"`와 `aria-checked`로 제공한다. 일부만 선택된 상태를 표현할 수 있는 일괄 제어는 세 상태 모두 토글 버튼의 `aria-pressed`를 유지한다. + 켜짐은 `--toggle-on-bg`, 꺼짐은 `--toggle-off-bg`, 핸들은 `--toggle-dot-color`를 사용한다. 다크 모드 활성 상태는 녹색 트랙과 짙은 핸들로 분리한다. 화면별 토글 색상 override는 금지한다. diff --git a/gui/design-system/foundations.md b/gui/design-system/foundations.md index 06f4066b06e..7ebb2382174 100644 --- a/gui/design-system/foundations.md +++ b/gui/design-system/foundations.md @@ -146,8 +146,11 @@ v1 토큰 `--radius-xs`, `--radius-sm`, `--radius-lg`는 Tailwind의 `rounded-xs | `--radius-card` / `rounded-card` | 10px | 카드, 타일 | | `--radius-panel` / `rounded-panel` | 12px | 패널, 섹션 | | `--radius-sheet` | 16px | 대화상자 | +| `--radius-full` / `rounded-full` | 9999px | 알약, 원형 버튼·점 | -알약(999px)은 상태 배지와 스위치에만 쓴다. +알약(999px)은 상태 배지와 스위치에만 쓴다. v1 스타일시트의 `--radius-pill`은 999px 그대로다. +`rounded-full`은 Tailwind 기본값 `calc(infinity * 1px)` 대신 유한한 9999px로 고정한다. 기본값은 +2147483647px로 빌드되고, macOS WKWebView가 이 값을 각진 모서리로 그릴 수 있다(#8281). ### Type diff --git a/gui/src/action-dialogs.ts b/gui/src/action-dialogs.ts index 534bd5b00dd..c51fc40518a 100644 --- a/gui/src/action-dialogs.ts +++ b/gui/src/action-dialogs.ts @@ -37,6 +37,8 @@ export interface ConfirmActionOptions { * the thing the user would be approving. Aborting resolves it as a refusal. */ signal?: AbortSignal; + /** Replacement focus target if the original trigger was removed while the dialog was open. */ + restoreFocusFallback?: (previouslyFocused: HTMLElement | null) => HTMLElement | null; /** Overrides the active locale; these dialogs open outside the React provider. */ locale?: Locale; } @@ -85,6 +87,7 @@ function openModal( cancelValue: T, resolve: (value: T) => void, signal?: AbortSignal, + restoreFocusFallback?: (previouslyFocused: HTMLElement | null) => HTMLElement | null, ): ModalShell { const messages = DICTS[locale]; const id = `opencodex-action-dialog-${++dialogSequence}`; @@ -121,12 +124,17 @@ function openModal( signal?.removeEventListener("abort", dismissOnNavigation); // The caller is answered whatever the teardown does. A dialog left connected is a // cosmetic fault; a promise that never settles hangs the handler that awaited it. + const focusWasInDialog = dialog.contains(document.activeElement); try { if (dialog.open && typeof dialog.close === "function") dialog.close(); dialog.remove(); // Only when the trigger is still in the document: a dismissal that removed the row // the button lived on would otherwise throw on the way out. if (previouslyFocused?.isConnected) previouslyFocused.focus(); + else if (focusWasInDialog) { + const fallback = restoreFocusFallback?.(previouslyFocused); + if (fallback?.isConnected) fallback.focus(); + } } finally { resolve(value); } @@ -209,7 +217,7 @@ export function confirmAction(options: ConfirmActionOptions): Promise { return new Promise((resolve) => { // Already withdrawn: answer without ever drawing the question. if (options.signal?.aborted) { resolve(false); return; } - const { form, bodyId, finish } = openModal(locale, false, resolve, options.signal); + const { form, bodyId, finish } = openModal(locale, false, resolve, options.signal, options.restoreFocusFallback); appendMessage(form, bodyId, options.message); const actions = document.createElement("div"); diff --git a/gui/src/client-icon.ts b/gui/src/client-icon.ts new file mode 100644 index 00000000000..bbfb56185aa --- /dev/null +++ b/gui/src/client-icon.ts @@ -0,0 +1,6 @@ +const MONOCHROME_CLIENT_ICONS = new Set(["grok.svg", "opencode.svg", "aside.svg"]); + +/** Invert only dark monochrome marks in dark themes; preserve colored brand marks. */ +export function clientIconClass(icon: string): string { + return `usage-source-mark${MONOCHROME_CLIENT_ICONS.has(icon) ? " usage-source-mark--mono" : ""}`; +} diff --git a/gui/src/components/ComboWorkspace.tsx b/gui/src/components/ComboWorkspace.tsx index 30572ea81a2..32ac369178b 100644 --- a/gui/src/components/ComboWorkspace.tsx +++ b/gui/src/components/ComboWorkspace.tsx @@ -130,14 +130,14 @@ export default function ComboWorkspace({ }; return ( -
+
)} + {authHandlers.pendingAffinityResets?.[item.name] && ( +
+ {t("prov.affinityResetPending")} + {authHandlers.onRetryAffinityReset && } +
+ )} {accountLoadState === "loading" && accounts.length === 0 && (