diff --git a/desktop/src-tauri/Cargo.lock b/desktop/src-tauri/Cargo.lock index 42f4b24ce4c..bfb9a1352a7 100644 --- a/desktop/src-tauri/Cargo.lock +++ b/desktop/src-tauri/Cargo.lock @@ -2645,7 +2645,7 @@ dependencies = [ [[package]] name = "opencodex-desktop" -version = "2.76.0" +version = "2.77.0" dependencies = [ "base64 0.22.1", "dbus", @@ -2665,6 +2665,7 @@ dependencies = [ "tauri-utils", "tokio", "uuid", + "winreg 0.55.0", ] [[package]] diff --git a/desktop/src-tauri/Cargo.toml b/desktop/src-tauri/Cargo.toml index c788432f9f3..f162e1d8411 100644 --- a/desktop/src-tauri/Cargo.toml +++ b/desktop/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "opencodex-desktop" -version = "2.76.0" +version = "2.77.0" description = "OpenCodex desktop shell" authors = ["OpenCodex contributors"] license = "MIT" @@ -38,6 +38,10 @@ tokio = { version = "=1.45.1", features = ["sync", "time"] } [target.'cfg(target_os = "linux")'.dependencies] dbus = "=0.9.12" +# Already present in the lock graph; only Windows writes its quoted Run command. +[target.'cfg(target_os = "windows")'.dependencies] +winreg = "=0.55.0" + [profile.release] codegen-units = 1 lto = "thin" diff --git a/desktop/src-tauri/src/first_run.rs b/desktop/src-tauri/src/first_run.rs index 3fc7616b93f..cf0c5b9e87c 100644 --- a/desktop/src-tauri/src/first_run.rs +++ b/desktop/src-tauri/src/first_run.rs @@ -6,8 +6,13 @@ use tauri_plugin_autostart::ManagerExt; const MARKER: &str = "start-at-login-claimed"; /// Marker file recording that the login item names the launch-origin argument. +#[cfg(not(target_os = "windows"))] const ORIGIN_MARKER: &str = "start-at-login-origin-flag"; +// Windows must revisit registrations claimed by the earlier, unquoted writer. +#[cfg(target_os = "windows")] +const ORIGIN_MARKER: &str = "start-at-login-quoted-origin-flag"; + /// What the one-time Start at Login decision did on this launch. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum StartAtLogin { @@ -63,7 +68,7 @@ pub fn apply_start_at_login_default(app: &AppHandle) -> StartAtLogin { if app.autolaunch().is_enabled().unwrap_or(false) { return StartAtLogin::AlreadyDecided; } - match app.autolaunch().enable() { + match crate::login_autostart::enable(app) { Ok(()) => StartAtLogin::Enabled, Err(_) => StartAtLogin::Unavailable, } @@ -99,7 +104,7 @@ pub fn adopt_launch_origin_argument(app: &AppHandle) { // leave a login launch showing its window forever. match app.autolaunch().is_enabled() { Ok(true) => { - if app.autolaunch().enable().is_err() { + if crate::login_autostart::enable(app).is_err() { return; } } diff --git a/desktop/src-tauri/src/lib.rs b/desktop/src-tauri/src/lib.rs index b8e6e9fe7d7..0dcd286717d 100644 --- a/desktop/src-tauri/src/lib.rs +++ b/desktop/src-tauri/src/lib.rs @@ -9,6 +9,9 @@ mod first_run; mod formatting; mod identity; mod logging; +mod login_autostart; +#[cfg(any(target_os = "windows", test))] +mod windows_autostart_command; // macOS only: it exists to replace one item in a menu no other platform installs. Compiling it // elsewhere would leave its contents unreachable, which -D warnings rejects. #[cfg(target_os = "macos")] diff --git a/desktop/src-tauri/src/login_autostart.rs b/desktop/src-tauri/src/login_autostart.rs new file mode 100644 index 00000000000..0e45bbd43ce --- /dev/null +++ b/desktop/src-tauri/src/login_autostart.rs @@ -0,0 +1,45 @@ +use tauri::AppHandle; + +/// All shell-owned enables use this entry point; the plugin still owns status and disable. +#[cfg(not(target_os = "windows"))] +pub fn enable(app: &AppHandle) -> Result<(), String> { + use tauri_plugin_autostart::ManagerExt; + app.autolaunch().enable().map_err(|error| error.to_string()) +} + +#[cfg(target_os = "windows")] +pub fn enable(app: &AppHandle) -> Result<(), String> { + use winreg::enums::{HKEY_CURRENT_USER, KEY_SET_VALUE, REG_BINARY}; + use winreg::{RegKey, RegValue}; + + let executable = std::env::current_exe().map_err(|error| error.to_string())?; + let executable = executable + .to_str() + .ok_or("autostart executable path is not Unicode")?; + let command = + crate::windows_autostart_command::command(executable, crate::startup::AUTOSTART_FLAG)?; + let name = &app.package_info().name; + let hkcu = RegKey::predef(HKEY_CURRENT_USER); + hkcu.open_subkey_with_flags( + r"SOFTWARE\Microsoft\Windows\CurrentVersion\Run", + KEY_SET_VALUE, + ) + .and_then(|key| key.set_value(name, &command)) + .map_err(|error| error.to_string())?; + + // Match the plugin's explicit-enable behavior when Task Manager has an override. + if let Ok(key) = hkcu.open_subkey_with_flags( + r"SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\StartupApproved\Run", + KEY_SET_VALUE, + ) { + key.set_raw_value( + name, + &RegValue { + vtype: REG_BINARY, + bytes: vec![2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + }, + ) + .map_err(|error| error.to_string())?; + } + Ok(()) +} diff --git a/desktop/src-tauri/src/tray.rs b/desktop/src-tauri/src/tray.rs index 479a5a90906..eb06e2dd1dc 100644 --- a/desktop/src-tauri/src/tray.rs +++ b/desktop/src-tauri/src/tray.rs @@ -208,7 +208,7 @@ pub fn install(app: &AppHandle) -> tauri::Result<()> { if enabled { let _ = app.autolaunch().disable(); } else { - let _ = app.autolaunch().enable(); + let _ = crate::login_autostart::enable(app); } } "stop-proxy" => { diff --git a/desktop/src-tauri/src/windows_autostart_command.rs b/desktop/src-tauri/src/windows_autostart_command.rs new file mode 100644 index 00000000000..4afa82b90fa --- /dev/null +++ b/desktop/src-tauri/src/windows_autostart_command.rs @@ -0,0 +1,41 @@ +/// Build the Run value without letting spaces split the executable path. +pub fn command(executable: &str, argument: &str) -> Result { + if executable.is_empty() || executable.contains(['"', '\0']) { + return Err("invalid executable path for Windows autostart"); + } + Ok(format!("\"{executable}\" {argument}")) +} + +#[cfg(test)] +mod tests { + use super::command; + + #[test] + fn quotes_program_files_path_and_keeps_launch_origin() { + assert_eq!( + command( + r"C:\Program Files\OpenCodex\opencodex-desktop.exe", + "--autostart" + ) + .unwrap(), + r#""C:\Program Files\OpenCodex\opencodex-desktop.exe" --autostart"# + ); + } + + #[test] + fn quotes_paths_without_spaces_and_preserves_unicode() { + for path in [r"C:\OpenCodex\app.exe", r"C:\用户\OpenCodex\app.exe"] { + assert_eq!( + command(path, "--autostart").unwrap(), + format!("\"{path}\" --autostart") + ); + } + } + + #[test] + fn rejects_paths_that_cannot_be_represented_in_a_run_command() { + for path in ["", "bad\"path.exe", "bad\0path.exe"] { + assert!(command(path, "--autostart").is_err()); + } + } +} diff --git a/desktop/src-tauri/tauri.conf.json b/desktop/src-tauri/tauri.conf.json index 688678b4f69..2e5a7813710 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.76.0", + "version": "2.77.0", "identifier": "com.opencodex.desktop", "build": { "frontendDist": "../ui", diff --git a/devlog/_fin/261003_antigravity_effort_families/000_plan.md b/devlog/_fin/261003_antigravity_effort_families/000_plan.md new file mode 100644 index 00000000000..273c22f2365 --- /dev/null +++ b/devlog/_fin/261003_antigravity_effort_families/000_plan.md @@ -0,0 +1,49 @@ +# Antigravity discovered effort families + +New Claude 5.5 Opus and Sonnet tiers currently appear as separate models because discovery only collapses families listed in the bundled catalog. This unit makes complete discovered low/medium/high families one model with selectable effort, including future versions, while preserving explicit suffix requests. + +- Class: C3, one cohesive PABCD work-phase (wp1); satisfy-spec. +- Trigger/goal: group the new Antigravity models and retain grouping during automatic refresh; publish and merge one ordinary PR into dev. +- Non-goals: upstream API invention, authentication changes, release, deployment, running-service restart, unrelated UI changes. +- Scope: current managed worktree; existing GitHub identity for authorized PR publication/integration. No user token/cost/wall-clock cap was set. +- Verifiers: focused parser/wire/catalog/new-model-policy regressions, typecheck, structure/privacy/layout gates, docs build, exact-head hosted CI and merge receipt. Existing wire baseline: 61 pass, 0 fail (`bun test tests/adapters/google/google-antigravity-wire.test.ts`). Tests name source arguments directly; typecheck includes src through tsconfig; Bun executes the targeted test files. +- Stop: implementation and independent review complete, relevant checks green, PR merged into dev. DONE needs these proofs; unresolved external checks remain unmet, never passed by assumption. +- Evidence: this unit and local .codexclaw receipts. Escalate only new authority or genuine unresolved design blockers. + +## Findings and decisions + +`src/providers/antigravity-models.ts:127` and `:207` require a bundled picker ID before recognizing a full suffix family. `src/codex/catalog/provider-models.ts:756` then publishes an empty effort ladder for newly discovered models even when their wire map is exact. Existing base-URL scoped discovery mappings already route effort and are generation-fenced. + +Reuse those owners. No new provider registry version list or GUI grouping implementation is needed. Saved suffix IDs remain wire-authoritative. Incomplete ladders stay directly routable; unknown single-wire/tiered semantics remain unknown. Existing explicit configuration overrides retain precedence. + +## Consultation + +Architect proposal/reflection pending from handle 01a10104-3fca-79a2-8f9c-a410eb64d57e (V1 logical read-only architect, inherited model). Main owns integration; independent reviewer will audit the final plan before implementation. + +Architect D01/D02/D03/D05/D06 accepted. D04 amended: carry exact discovered family evidence with CatalogModel; normalize only discovery baseline and base disabled state through the existing reconciliation/persistence path. Keep provider selectedModels unchanged; a read-only shared visibility projection recognizes selected suffixes. Preserve raw default/combo references. No provider configuration migration or extra persistence surface. All-disabled suffixes transfer disabled status once; any enabled known suffix preserves an enabled base. A previously known base and explicit base disable win on later refreshes. + +Reflection: D01/D02/D03/D05/D06 ALIGNED; D04 gap (retained suffix IDs reappearing as new arrivals) folded into final plan by normalizing both policy input and baseline. Same architect recheck requested; no implementation before resolution and independent A audit. + +Final same-architect reflection: ALIGNED for D01-D06, no remaining material design gap. Baseline additionally passed typecheck and 29 listing/policy tests. Proceed to independent A audit. + +A round 1: FAIL, one accepted blocker: final merge independently filters raw selectedModels. Added retained-sync and convergence consumers and final-merge regression. No other material design blocker. Same reviewer re-audit requested. + +## Build and verification + +Implemented the audited plan. A bounded worker owned the family helper/policy/tests; main integrated parser, catalog, both final-merge selection callers and management projection. Discovery tests first failed 8/8 on the original source and passed after the fix. Existing future-family expectations were updated to assert the requested grouping. OpenCodex's existing synthetic ultra orchestration level remains separate from upstream low/medium/high; no unsupported synthetic max is added to the discovered ladder by default. + +Focused verification: 43 isolated test files, 830 pass / 0 fail, covering all Google adapter tests, family/policy/persistence, management and final merge, layout, file-size and optional-Lab import boundaries. The initial combined-process run had 803 pass / 2 fail: new test names disagreed with seed ownership (fixed by provider-prefixed names); an unchanged gemini-web-search mock replaced the listing test's OAuth token across files (each file passes in its own process). Full local suite is disproportionate for this bounded catalog change and shared workstation; repository resource exception uses these affected regressions, with full platform coverage left to exact-head hosted CI. Local logs stay in ignored scratch. + +Typecheck passed. Docs build passed (561 pages and 77,923 internal links). Structure and privacy scans passed. Structure catalog was already at its 600-line budget, so the shared family contract lives in providers-and-adapters with a link from the existing catalog paragraph. + +## Reviewed implementation outcome + +Independent implementation reviewer checked commit `916bd9d600723375c3c5667f4fe7b085af962c4d`, found no concrete regression, and independently ran all 28 new family tests with zero failures. The parser, cache, compatibility, policy projection, both final merge filters, and public list callers are covered. No live upstream model request was made; tests exercise the supplied wire-ID contract with controlled CCA discovery fixtures. + +Implementation is complete and published in PR #6501: https://github.com/lidge-jun/opencodex/pull/6501. The PR's live Verification section carries the final hosted-CI and authorized maintainer-integration receipt. Final goal completion additionally requires that exact-head gate and verified dev merge; neither publication nor this archived implementation record claims those external steps already passed. + +## External-review corrections + +The initial PR head passed hosted CI, but external review found a restart/outage defect that the in-memory tests missed. Accepted and corrected: a bounded exact-family snapshot now precedes synthetic catalog publication, restores routing after restart, and cannot revive after generation invalidation or an authoritative empty/partial replacement. Private no-follow atomic replacement and destination hashes keep URLs and credentials out of the snapshot. Write failure retains prior coherent discovery state. Overlapping family identities are preserved in public rows, policy and original-only selection projection. + +The same architect confirmed the revised D05 design ALIGNED. Independent code/security re-review closed both findings and passed 35 then-current focused tests. Main subsequently added two boundary assertions and confirmed 839 tests across 44 affected files (per-file isolation plus the final focused additions), with typecheck and structure checks passing. Restart tests use separate processes and the actual Google adapter while discovery throws, including each tier, medium default, corruption, size bounds, destination/home separation, tombstones and write-failure publication. Process restart is verified; power-loss durability is not claimed. Final updated-head hosted CI and merge receipt remain in the PR's Verification section. diff --git a/devlog/_fin/261003_antigravity_effort_families/010_discovery_grouping.md b/devlog/_fin/261003_antigravity_effort_families/010_discovery_grouping.md new file mode 100644 index 00000000000..cc18ff35837 --- /dev/null +++ b/devlog/_fin/261003_antigravity_effort_families/010_discovery_grouping.md @@ -0,0 +1,48 @@ +# wp1: evidence-driven effort-family grouping + +Depends on existing AntigravityAvailableModel and generation-fenced mapping contracts; no new persisted schema. + +## File change map + +- MODIFY `src/providers/antigravity-models.ts`: replace bundled-membership requirement for low/medium/high triples with validated complete-family evidence; use the same predicate for parsing and effort map construction. Keep explicit irregular maps and known single `-tiered` handling. Choose stable medium default for a newly discovered complete family; preserve saved suffix IDs and legacy Gemini/4.6 rules. +- MODIFY `src/codex/catalog/provider-models.ts`: derive discovery reasoningEfforts from the exact effort wire map instead of always `[]`, preserving provider hints and cache behavior. +- NEW focused regression in `tests/adapters/google/` and catalog integration in `tests/providers/`; register each in `scripts/test-layout/layout.json` and `tests/fixtures/test-layout-expected.json`. +- MODIFY `structure/providers-and-adapters.md`, `structure/catalog.md`, and canonical `docs-site/src/content/docs/guides/providers.md` for the precise grouping contract; review mapped dependent docs. +- Selection/new-model-policy treatment will be fixed in the plan after architect review before A; no configuration rewrite is authorized by this provisional line. + +## Acceptance scenarios + +1. Both 5.5 families and synthetic future version triples collapse to exactly one base row each, regardless of label or order; mapped low/medium/high route to matching wire IDs without thinkingLevel. +2. Partial families and standalone `gpt-oss-120b-medium` remain separate/direct; unrelated IDs and unknown single `-tiered` models retain existing behavior. +3. Saved explicit `-low` with high reasoning continues to send the low wire ID without contradictory thinkingLevel. +4. Catalog publishes exact discovered efforts plus context/image metadata; cached rereads and forced refresh retain grouping; old discovery mapping is invalidated with cache generation. +5. Existing 4.6 and Gemini cases remain passing. Selection/new-model-policy cases are finalized before independent audit. + +Verification uses focused tests, `bun run typecheck`, `bun run structure:check`, `bun run privacy:scan`, layout guards, docs build, and final-head hosted suite. Full local suite may use the repository resource exception if disproportionate; document the executed scope and coverage left to hosted CI. + +## Final D04 refinement and delegation + +Replace the provisional selection line above with these concrete changes: +- MODIFY `src/codex/catalog/parsing.ts`: optional `antigravityEffortWireModelIds` on CatalogModel, containing low/medium/high wire IDs. Creation: parser map -> provider-models. Serialization/deserialization: in-memory model-cache retains typed rows unchanged; no config schema or disk cache addition. Consumers: pure Antigravity family helper, public-list projection and new-model-policy reconciliation. Public Codex serialization continues its explicit field selection. +- NEW `src/providers/antigravity-effort-families.ts`: dependency-light pure helpers over structural rows; validate complete exact family maps. Deduplicate retained suffix rows only when the same provider/base evidence exists. Compute selected alias matches and inherited disabled state without modifying provider configs. +- MODIFY `src/providers/new-model-policy.ts`: before applyNewModelPolicy, normalize prior suffix baseline to base for evidence-backed families and transfer all-disabled state once; keep suffix disable entries. Existing base evidence has precedence. Only authoritative live providers participate; genuinely new families still obey new-model policy. +- MODIFY `src/codex/catalog/model-visibility.ts`, `src/codex/catalog/aggregation.ts`, `src/server/management/model-rows.ts`: reuse shared family helper for selected/disabled/public dedupe behavior. Internal retained combo evidence remains available until public projection. +- NEW `tests/providers/provider-antigravity-effort-families.test.ts`: pure projection and reconciliation scenarios. Dedicated worker may own this helper, policy hookup, and this test only; main owns parser/catalog/type/callers/docs/registration. No overlapping writers or worker Git operations. + +D05: for a collapsed family use conservative context (minimum only when every tier provides it) and image support (true only when every tier says true, false when any explicitly false, otherwise unknown); use medium default independent of discovery ordering. Explicit caller/provider reasoning override still wins. Avoid inventing single-wire future -tiered semantics. + +Additional acceptance: old suffix baseline + policy off + high enabled -> base enabled and not a new arrival; all disabled -> base disabled exactly once; later user re-enable stays enabled; suffix allowlist selects base; retained/combo suffix stays internally routable but appears once publicly; genuinely new family remains off under policy off; unrelated providers/custom rows never inherit family rules. Tests exercise actual cache and management projection paths. Same architect reflection and independent audit required before Build. + +Architect reflection D04 gap accepted: before applyNewModelPolicy, normalize BOTH prior baseline and policy-only discovered IDs through the same observed family mapping. Retained/combo suffix rows remain internally available but never reappear as new policy arrivals. Repeated reconciliation must be a no-op with such retained rows present. + +Existing contract expectation update: `tests/adapters/google/google-models-listing.test.ts:133` currently expects complete future-flash low/medium/high separately. Replace with one future-flash plus exact effort map assertions; this is the requested behavior change, not weakened coverage. Add adapter-level request body checks for 5.5. + +Independent A blocker accepted: final catalog merge has another raw selectedModels consumer. MODIFY `src/codex/catalog/retained-sync.ts` and `src/codex/convergence.ts` to pass the same evidence-backed allowlist projection into final merge, keeping persisted selection unchanged. Add a regression that uses the projected allowlist with the actual `mergeCatalogEntries` final filter, proving base survives suffix-only selection. This adds consumers of D04's existing projection, not a new data contract or persistence flow. + +## C review corrections + +External review exposed a real restart/outage gap: newly synthesized base IDs lost their exact wire mapping when process memory was empty. The first hosted run passed, but delivery remains blocked until durable mapping restoration is implemented and verified. The initial no-persistence D05 decision is reopened for architect reflection and independent review. Candidate: a bounded non-secret wire-map snapshot under the existing config home, destination-hash keyed, replacing only after valid current-generation discovery. Restore before first live observation; never revive an invalidated or authoritatively empty map. Exact legacy suffix requests retain their identity. + +A second accepted finding affects overlapping complete families (`foo`, `foo-low`): public dedupe must keep rows that carry their own full family evidence, and policy normalization must prefer observed base identities over tier aliases. The focused regression reproduces the old disappearance and requires both identities to remain unchanged through reconciliation. + +Final correction design (same architect ALIGNED): add `src/providers/antigravity-wire-snapshot.ts`, using existing config paths/private no-follow atomic writer. Store version, bounded non-secret provider ID and exact complete suffix maps under a destination-hash filename. Preserve URL path case and scope memory keys to config home plus destination. Generation check -> synchronous atomic snapshot -> in-memory registration -> setCached, with no await; persistence failure publishes no new synthetic cache rows. Restore once before live observation, binding restored data to the provider's current cache generation; retain invalidation tombstones. Accepted partial/empty snapshots replace old families. Reads validate bounded bytes/counts/IDs and fail closed. Restored mappings affect routing only, never discovery authority or availability. New restart-subprocess tests must prove adapter wire selection while discovery is unavailable, as well as empty replacement, invalidation, corrupt-file and destination isolation. Overlap selection consults only the original allowlist and ignores tier aliases that are themselves evidenced bases. diff --git a/devlog/_fin/261003_claude_1p_agents/000_plan.md b/devlog/_fin/261003_claude_1p_agents/000_plan.md new file mode 100644 index 00000000000..760f52fe1e8 --- /dev/null +++ b/devlog/_fin/261003_claude_1p_agents/000_plan.md @@ -0,0 +1,33 @@ +# Restore roster registration after Claude first-party setup + +First-party setup can report success without regenerating the selected OpenCodex agents. Reconcile the existing roster at the successful setup boundary so a subsequent Claude session can discover its routed workers. This fixes a demonstrated missing lifecycle step; it does not claim to explain every missing-agent report. + +## Loop specification + +- Class: C4 care for the management API surface; one satisfy-spec PABCD cycle, wp1. +- Trigger and goal: reported missing third-party subagents after Claude 1P connection; restore registration and merge a focused PR to dev. +- Non-goals: no new settings, catalog schema, GUI redesign, account changes, release, installation or live configuration mutation. +- Verifiers: isolated management/first-party regression tests observe generated files and rejected writes; typecheck checks the TypeScript tree; privacy and structure gates check repository contracts; exact-head hosted CI gates merge. No live provider request needed. +- Stop: reviewed fix, passing required CI and verified dev merge. DONE requires that evidence; unresolved failure is reported honestly, never as passing. +- Artifact: this unit and 010_registration.md; scratch command output stays in .tmp/claude-1p. +- Escalation: new routing/auth semantics or an irreconcilable client-specific report requires a revised scope. No token/cost/wall-clock budget was supplied; use existing local tools and authenticated repository access only. + +## Evidence and decisions + +- D1: accept existing syncClaudeAgentDefsBestEffort as the registration owner. The dedicated CLI 1P return at src/server/management/agent-settings-routes.ts:1681 bypasses its ordinary call at line 1952. +- D2: preserve all rejection/rollback, disabled integration/injection and sibling-ownership behavior. Do not change low-level first-party settings reconciliation. +- D3: cover the equivalent Desktop first-party apply success boundary if confirmed; no gateway behavior changes. +- Architect: inherited V1 agent 01a0fff4-fd56-73e3-8aaf-1d9c594927ed. Initial GUI navigation proposal rejected because no hidden-control gate was demonstrated. Runtime tracing identified the skipped registration step instead. Reflection: ALIGNED; main folded exact Desktop placement, pre-existing-file preservation and best-effort wording into 010. Independent audit pending. +- Main verified buildClaudeAgentDefs can build the configured roster without writing files. This does not establish why startup did not leave files in a particular installation. + +## Delivery + +One ordinary PR to dev. Main owns branch/commits/CI/merge. Inherited worker owns runtime and tests; main owns documentation and independent review coordination. No new dependencies. The user subsequently prohibited all local testing. Final acceptance is exact-head hosted CI plus source-only review; no further local tests, builds, typechecks or QA are permitted. + +Baseline verifier evidence: `bun run test -- tests/claude-integration/claude-management-api.test.ts tests/claude-integration/claude-desktop-first-party.test.ts tests/claude-integration/claude-agents-inject.test.ts tests/claude-integration/claude-agent-startup-sync.test.ts` exited 0 (123 pass, 0 fail). Each explicit argument observes an owning route/generator/startup surface. Initial wrapper failure was the unexecuted pinned Bun dependency postinstall after ignore-scripts; running its checked-in dependency installer restored the wrapper. No lockfile changed. + +Independent A review: inherited reviewer 01a0fffb-254e-7563-8a0f-77ab1d70ca03 returned PASS with no blockers. Explicit security scope covered management admission, refusal/rollback exits and helper ownership gates. No auth or credential format changes are planned. + +## Verification correction + +The user prohibited local tests during C. The in-progress full suite was terminated (exit 143), not counted as passing. Local checks reported above predate this correction and are historical only. Remaining verification and final acceptance use GitHub Actions against the PR head. The implementation remains the audited two-call reconciliation fix. diff --git a/devlog/_fin/261003_claude_1p_agents/010_registration.md b/devlog/_fin/261003_claude_1p_agents/010_registration.md new file mode 100644 index 00000000000..2d04473789d --- /dev/null +++ b/devlog/_fin/261003_claude_1p_agents/010_registration.md @@ -0,0 +1,24 @@ +# wp1: Reconcile registered agents at successful first-party setup + +## File changes + +- MODIFY src/server/management/agent-settings-routes.ts: in the dedicated cliFirstParty branch, after reconciliation succeeds and rollback/refusal paths exit, await the existing syncClaudeAgentDefsBestEffort before response. Apply to successful on/off writes so idempotent saves repair missing files; disabling first-party alone keeps registered agents when the integration remains enabled. +- MODIFY the same file: for Desktop first-party apply, reconcile after picker reconciliation immediately before the success response, guarded by modeSaved.ok; saved:false partial success must not register. Reuse the existing helper; never put filesystem registration in the settings transaction or TLS startup primitive. +- NEW tests/claude-integration/claude-first-party-agent-sync.test.ts if existing large test owners cannot accommodate focused cases; use isolated config directories and real management API/file assertions. Register in scripts/test-layout/layout.json and tests/fixtures/test-layout-expected.json. +- MODIFY docs-site/src/content/docs/guides/claude-code.md and structure/runtime.md: state successful first-party setup attempts best-effort reconciliation of the owned roster and new sessions load it. Review adjacent translated roster prose and update contradictions only. + +## Activation and proof + +1. Empty isolated agents directory, non-empty configured routed roster, CLI first-party enable succeeds: expected marker-owned file exists before response, with correct ocx-route directive. +2. Repeat successful enable after removing only the test fixture's generated file: file repaired. +3. injectAgents=false: no registration; existing owned file pruned according to existing policy, user-authored file retained. +4. Refused or rolled-back enable: no registration is created, and pre-existing files remain byte-identical; rollback checks retain existing semantics. +5. Successful CLI first-party disable with enabled integration: roster retained. +6. Existing generator/startup tests must continue proving enabled:false and sibling-ownership gates. +7. Successful Desktop first-party apply with persisted mode: roster generated; persistence failure never adds a new registration side effect. + +Verification is now hosted-only by explicit user correction. GitHub CI must execute the new focused test through its registered domain and the applicable existing suites, typecheck, structure/privacy gates and documentation build. Do not execute these locally. Baseline GUI tests (20 pass) were investigation evidence only, not proof of this fix. No GUI change planned, so screenshot requirement is inapplicable. + +## Security review scope + +Preserve existing management admission and settings-file ownership. The existing generator writes only marker-owned definitions; regression tests exercise refusal and disabled registration. No credentials, request bodies or private local configuration enter repository artifacts. Independent security review must confirm the new call occurs only after successful reconciliation and does not weaken consent or ownership checks. diff --git a/devlog/_fin/261003_claude_1p_agents/090_summary.md b/devlog/_fin/261003_claude_1p_agents/090_summary.md new file mode 100644 index 00000000000..0fb8c6d9900 --- /dev/null +++ b/devlog/_fin/261003_claude_1p_agents/090_summary.md @@ -0,0 +1,9 @@ +# Implementation outcome + +The dedicated CLI first-party save and persisted Desktop first-party apply paths now attempt the existing best-effort agent-roster sync after settings/picker reconciliation. This closes the missed registration step without changing settings, authentication or agent ownership policy. + +The ten regression cases failed six times before the runtime edit and passed after it. The existing related suites, typecheck, structure/privacy gates and documentation build also passed before the user prohibited further local testing. The subsequent local full-suite run was terminated with exit 143 and is not acceptance evidence. + +Independent source/security review of implementation commit `25dd0288893d85f9bdf4d27f429c9cec0842abfa` covered all nine changed files and returned PASS with no blockers. A file-write failure after successful setup remains best-effort, as in the existing helper; no new success guarantee is claimed. + +Delivery is [PR #6484](https://github.com/lidge-jun/opencodex/pull/6484). Required hosted checks for its final head and the maintainer integration decision are recorded there. Final acceptance is hosted-only. No release, installed-app update or live Claude-session validation is claimed. The missing setup-time sync is reproduced; the historical cause of every absent local definition is not established. diff --git a/devlog/_fin/261003_cli_help_ux/000_plan.md b/devlog/_fin/261003_cli_help_ux/000_plan.md new file mode 100644 index 00000000000..4ff2158c826 --- /dev/null +++ b/devlog/_fin/261003_cli_help_ux/000_plan.md @@ -0,0 +1,224 @@ +# Make existing CLI commands discoverable + +OpenCodex already has broad commands, but its 92-line entry help hides the next +step and nested help drops the user's requested path. This unit adds a complete +reference escape, preserves explicit help paths, presents common tasks first, +and makes failed lookups recoverable without executing a correction. + +## Loop specification + +- Archetype: satisfy-spec, C3 CLI presentation and argument classification. +- Trigger: user requested a cxc-loop and stacked PRs, then explicitly narrowed the + work to UX after `ocx --help`, excluding client support additions. +- Goal: readable root, useful family/declared leaf help, and concise recovery. +- Non-goals: new clients/providers, GUI/TUI, runtime command redesign, JSON schema + changes, credentials, live proxy changes, package updates, release or merge. +- Verifier: focused CLI tests, isolated terminal transcripts, typecheck, + test:changed, structure and generated surface checks, privacy scan, docs build, + independent review, and each PR's current-head hosted checks. +- Stop: agreed three layers published with validation/review evidence; unresolved + external CI is recorded truthfully and prevents a ready/passing claim. +- Memory artifact: this numbered unit and ignored `.tmp/cli-ux/` raw logs. +- Outcomes: DONE means implemented, checked and published; external blocking or + resource exhaustion preserves unfinished work and evidence, never weakens scope. +- Escalation: main resolves architectural findings; new external authority needs + the user. No user token/time cap was specified; use available host limits. +- Permissions: inherited-model leaf agents may read or edit assigned disjoint + files; main owns Git and FSM. Push/PR creation authorized; merge is excluded. + +## Structure and dependencies + +```text +src/cli/registry.ts + capabilities.ts (existing declarative owners) + -> help-catalog.ts + help-models-context.ts (pure help resolution/data) + -> help.ts (human rendering) <- root.ts (early help classification) + -> help-navigation.ts (root/family discovery) + -> help-recovery.ts (bounded suggestions) <- dispatch.ts unknown-root exit +``` + +No runtime command imports enter the help renderer. Command parsers remain the +execution authority; capabilities are incomplete and are never called a complete +command tree. The complete reference retains all existing public banner rows. + +| Work phase | Deliverable | Dependency | Branch / PR base | +| --- | --- | --- | --- | +| wp0 | Audited docs-only roadmap | baseline | first layer carries plan docs | +| wp1 | Explicit help paths, full reference, shared context usage | wp0 | `codex/cli-ux-help-foundation` -> `dev` | +| wp2 | Compact root and family discovery | wp1 | `codex/cli-ux-navigation` -> foundation | +| wp3 | Contextual recovery without automatic execution | wp2 | `codex/cli-ux-recovery` -> navigation | +| wp4 | Review, publication and hosted proof | wp3 | evidence/docs on stack tip | + +Branches form an ordinary manual chain; no GitHub native stack registration. +Each layer owns its tests and user-facing documentation. Lower-head changes must +cascade into descendants before publication/readiness. + +## Decisions and consultation + +Architect handle: `01a100e3-732b-7300-b5a3-1286e55db053` (inherited settings, +logical architect on V1; no claim of a separate native sandbox/model family). + +- CLI-UX-01 accepted: preserve full help before compacting root; carry nested + paths and supplement registry with capability help. Amended: shared exact + context usage is imported by its runtime owner, not duplicated. +- CLI-UX-02 accepted: compact root, family discovery, no provider self-loop. +- CLI-UX-03 accepted: contextual conservative suggestions, never execute them. +- CLI-UX-04 accepted: no updater/bin changes or competing config/usage fixes. + +Concrete plan revision: these 000/002/010/020/030/040 documents. Same-architect +reflection found the deliberately unregistered `internal` runner must be +explicitly admitted before unknown-root rejection; CLI-UX-03 now records that +exception and a regression. Bare-help positions and the whitespace-sensitive +models runtime usage consumer were also clarified. Updated reflection and +independent audit will be recorded before implementation. +Final same-architect reflection: ALIGNED on CLI-UX-01 through CLI-UX-04; +no remaining architecture-plan gaps (2026-10-03). + +Rejected alternatives: an interactive wizard adds state to routine help; importing +command handlers risks side effects; replacing all parsers or generating a full +command tree from incomplete capabilities would expand scope and fabricate grammar. + +## Scope boundaries and verification + +The missing-CODEX_HOME import failure is recorded but deferred: changing import +bootstrap/path validation is a separate lifecycle change. Tests use existing empty +homes and prove no writes. Bare `help` in later operand positions and `--` +pass-through get explicit classification coverage; do not globally change +`hasHelpFlag` for unrelated callers. These delimiter guarantees apply to the +Bun CLI head: the unchanged published Node updater guard scans later arguments +even beyond `--` (bin/ocx.mjs:950). Do not claim launcher-wide parity. + +Any new test file is added to both test-layout manifests. The 600-line runtime +structure doc is updated by replacement/consolidation, never by raising its cap. +Full local testing follows repository policy; if resource contention makes it +impractical, record exact focused proof and leave broader coverage to CI in draft. + +## Continuity + +wp0 in progress: source/UX evidence in 001; design in 002; all later phases are +specified in the decade documents. No production implementation yet. + +Independent A reviewer `01a100ec-f604-7710-b4f6-c625286d646d`: GO-WITH-FIXES +(blockers=1). Main accepted the verified missing wp3 migration of the existing +unknown-help stdout-banner assertion; 030 now explicitly preserves and updates +that regression. No design decision changed. Source/plan coverage complete. + +Fresh-reader UX reviewer `01a100ef-71b8-7c21-a4ce-3c98534289c1` understood +the problem/journey and requested two clarifications. Main folded partial-family +coverage wording and concrete distant/nested/undeclared-help examples into 002/030. + +## wp0 Done + +Roadmap locked: three dependent CLI UX implementation layers, no client additions. +Independent final docs audit PASS; fresh-reader recheck CLEAR. The seven-document +shape verifier and whitespace check pass. Baseline source tests/typecheck and +structure/surface/docs build are evidence of available verifiers, not changed UX. +No production code changed. The rejected direction was adding client support; +the user's corrected objective is terminal command discovery and recovery. +Next direction: revalidate 010 against the unchanged runtime source and implement +wp1's explicit help paths/full reference without shortening the root yet. + +wp1 B amendment: existing mutating-help regression exposed a compatibility gap. +Flag-appended help falls back to known parent when detailed metadata is missing; +explicit `ocx help ` remains strict. This keeps service/shim help safe and +successful. Exact amendment and activation tests are in010. +Same architect reflected ALIGNED on wp1's compatibility amendment after tracing +runCli -> renderer and real CLI safety fixtures. Builder reports 85 focused tests +passing plus the final explicit service-install regression; main verification and +independent implementation review remain pending. + +## wp1 Done (implementation scope; review readiness pending) + +Explicit help paths, complete reference, shared context grammar, safe appended +flag-help fallback, tests and docs are implemented. Independent review PASS and +all affected-scope gates/11CLI scenarios pass. Full local suite has four failures, +three reproduced on untouched baseline and one unresolved snapshot failure; +011 records them without a waiver. Publication is draft until wp4 resolves the +required hosted evidence. The loop goal remains active. +Next direction: consume020 to make root/family navigation concise while retaining +wp1's full reference and compatibility behavior. No service/runtime changes are +inferred from the unrelated verification investigation. + +wp2 P revalidated020 against de02ff1d00. Accepted architect CLI-UX-02 amendments: +entry canonical/declared-child presentation fields, curated context link, updated +root/full tests, incidental old error-banner behavior, and both provider usage +consumers consolidated with an explicit successful-help output sink. Same +architect final reflection ALIGNED; no execution/parser/JSON scope expansion. +wp2 independent A review PASS atde02ff1d00; both provider usage consumers, +transient sink/entry metadata, test migrations and draft limitations reviewed. +Main clarified that92is a measured full-reference baseline, not a permanent +assertion; visible registry coverage protects future command additions instead. +## wp2 Done (implementation scope; PR remains draft) + +Root help is26logical lines/max80columns, with standard usage, common tasks, +registry-backed descriptions and the full-reference escape. Family/alias links, +curated context topic and provider single-owner help are implemented. Both +provider output channels and appended-help fallback remain correct. + +Five meaningful RED tests preceded135focused passes. Changed-import verification +initially exposed a complete-reference test still reading compact help; preserving +all assertions and switching its renderer fixed it. Final:1,097pass/1skip/0fail +across46files. Typecheck, structure, skill surface, privacy, layout18tests and +docs561pages/77,929links pass.18real CLI QA scenarios and a40x24PTY capture confirm +outputs, exits, no ANSI dependency and no state writes. PTY, child and temp home +were closed/removed. Independent reviewer inspected16/16files:PASS. + +The full local suite's prior four failures remain recorded in011; no full-green +or merge-readiness claim. PR6498's current head has23successful checks and8 +workflow-policy skips, including a successful CI aggregate and format gate. +Next direction: consume030 for concise contextual recovery, keeping all wp1/wp2 +behavior. Publication uses ordinary dependent draft PRs; no merge. + +wp3 P revalidated030 against6f92cc1c98. Accepted CLI-UX-03 amendments: catalog +owns canonical recovery candidates, token/depth/distance/output bounds are +explicit, redaction protects first-token echo, successful fallback/sink order +is preserved, unknown-root rejection stays in runCli without a new head kind, +and both legacy banner tests plus direct dispatch have regression coverage. +Same architect final reflection ALIGNED; no additional runtime scope. +wp3 independent A audit PASS: candidate owner, pure redactor, bounded diagnostics, +fallback/sink ordering, early rejection/internal exemption and test activation +reviewed against6f92cc1c98. No source-backed blockers; localfull caveat retained. +## wp3 Done (implementation scope; PR remains draft) + +Unknown roots and strict unavailable help now emit concise stderr-only recovery +and exit1. Canonical metadata owns suggestions, with bounded matching/depth and +no automatic execution or trailing-operand echo. Unknown roots stop before shim +preflight; registered aliases, hidden roots and internal remain admitted. Existing +successful parent fallback/sink, provider behavior and capability JSON remain. + +Six RED tests preceded146focused passes. Main changed-import check:1,113pass, +1skip,0fail across48files. Typecheck, structure, skill surface, privacy, layout18 +and docs561pages/77,932links pass.24real CLI scenarios confirm output, exit codes, +case/distance/control behavior, equivalent help and no state writes; teardown +verified. Independent review covered14/14files and passed2,483 oracle comparisons, +75 hostile-input cases,15isolatedCLI probes and73preflight admissions. + +The navigation layer's hosted failure was a subprocess-only full-help consumer. +One argv migration preserved all restore assertions; exact-layer snapshot tests +passed5/5 and independent review passed2/2files. Parent navigation advanced to +aaf3672bd0 before this layer's source commit, preserving stack ancestry. + +Local full-suite four-failure limitation remains in011. No full-green/merge-ready +claim. Next direction: finish040 publication/stack maps, inspect every current +head's CI, record the terminal published-draft outcome and close the loop. + +wp4 P revalidation accepts the delivery architect's five amendments in040: +reuse6498/6500/6503, separate topology/native membership, retain concrete draft +limitation, bind CI to actual publication heads, and archive only a recorded +published-draft outcome with final-head receipt outside the self-referential doc. +No remaining implementation scope is inferred from this delivery phase. +Delivery architect final reflection: ALIGNED on040. Fresh read-only PR snapshots +confirm three existing drafts; local ancestor checks pass for both dependency +edges. Final-head CI will be re-read after the closure/archive commit. +041 CI prerequisite plan: independent source/cause/regression audit PASS and same +architect delivery reflection ALIGNED. Capture one clock observation; keep all +assertions/timeouts. Separate prerequisite plus the three retained UX PRs will +be restacked serially with explicit old-head leases and fresh CI on every head. + +CI prerequisite published as6506 at19136566a3. Deterministic RED received6001 +instead of6000; after the one-clock fix,43tray tests/117assertions, typecheck, +structure and privacy checks pass. Independent three-file review and five extra +boundary probes pass. No timeouts/assertions were relaxed. +The owned UX heads were restacked atomically with explicit old-SHA leases: +foundation b82c5a9d9f, navigation13c0e829df, recoveryd642e2f78f. Range-diff proves +all five UX commits patch-equivalent to their pre-rewrite counterparts. Foundation +PR6498 now targets the prerequisite branch;6500/6503 keep their predecessor bases. diff --git a/devlog/_fin/261003_cli_help_ux/001_evidence.md b/devlog/_fin/261003_cli_help_ux/001_evidence.md new file mode 100644 index 00000000000..0f5f2b3082d --- /dev/null +++ b/devlog/_fin/261003_cli_help_ux/001_evidence.md @@ -0,0 +1,65 @@ +# CLI help evidence + +## Scope and provenance + +The requested outcome is terminal command UX starting at `ocx --help`. The later +scope correction excludes new client integrations. Publishing a manual PR stack +is authorized; merging, releasing and changing the running proxy are not. + +OpenCodex baseline: `4b98328dca` (`origin/dev`, fetched 2026-10-03). +Reference: https://github.com/yetone/magpie at +`b3ab3e42e9770a09a3ec29ac9c07511376d4e7fd`, cloned into ignored scratch space. +The reference informs interaction design; this is an original TypeScript +implementation using OpenCodex's existing command contracts. + +## Observed help behavior + +Source invocations used Bun with isolated, existing `OPENCODEX_HOME` and +`CODEX_HOME` directories, `NO_COLOR=1`, `TERM=dumb`, and `COLUMNS=80`. + +| Invocation | Exit | Observation | +| --- | --- | --- | +| `ocx --help` | 0 | 92 lines; one flat command list mixes setup, diagnostics and advanced recovery. | +| `ocx models context --help` | 0 | 12-line parent models help; the requested nested path is lost. | +| `ocx help models context` | 0 | Same parent output; the second path component is ignored. | +| `ocx account --help` | 0 | Parent usage plus manually maintained detail rows. | +| `ocx help modles` | 1 | Unknown-command stderr followed by the entire 92-line root help on stdout. | + +Local raw outputs: `.tmp/cli-ux/baseline/`. These scratch files are not published. +The first baseline attempt lacked installed dependencies and existing isolated +homes; those environmental failures are not product findings. Dependencies were +then installed from the frozen lockfile with lifecycle scripts disabled, and the +successful observations above were captured anew. + +## Existing owners + +- `src/cli/root.ts`: pure head classification and help/version exits before shim + reconciliation. It currently stores only one `helpTarget` string. +- `src/cli/help.ts`: manual root banner and top-level registry help rendering. +- `src/cli/registry.ts`: command names, aliases, usage and summaries. +- `src/cli/capabilities.ts`: declarative command paths, flags and API semantics; + intentionally imports no command modules. Coverage is explicitly incomplete. +- `src/cli/dispatch.ts`: canonical runners and aliases; ordinary dispatch remains + separate from presentation. +- `docs-site/src/content/docs/reference/cli.md`: public command discovery contract. +- `structure/runtime.md`: entrypoint and CLI lifecycle contract. + +## Baseline verification + +`bun test tests/cli/cli-help.test.ts tests/cli/cli-registry.test.ts +tests/cli/cli-capabilities.test.ts tests/cli/cli-capabilities-arguments.test.ts` +completed with **55 pass, 0 fail, 705 assertions**. Each named test directly +observes the existing help, registry or capability surface. + +## Concurrent work excluded + +Open PR #6483 owns config flags-only defaults; #6436 owns usage-report row limits. +This unit does not change either command's runtime semantics. + +Additional baseline gates: `bun run typecheck`, `bun run structure:check`, +`bun run skill:surface:check`, and `cd docs-site && bun run build` exited 0. +The docs build produced 561 pages and checked 77,923 internal links. Typecheck +includes src via tsconfig.json; structure checks ownership/doc contracts; skill +surface checks the generated capability inventory; docs build observes the CLI +reference page through the Astro content collection. These establish executable +verifiers, not proof of the not-yet-implemented UX. diff --git a/devlog/_fin/261003_cli_help_ux/002_terminal_design.md b/devlog/_fin/261003_cli_help_ux/002_terminal_design.md new file mode 100644 index 00000000000..bb37073c5de --- /dev/null +++ b/devlog/_fin/261003_cli_help_ux/002_terminal_design.md @@ -0,0 +1,43 @@ +# Terminal design and consultation evidence + +Read this as a command index for a first-time operator and a returning terminal +user. Use plain labels, indentation and whitespace. Root answers what to do next; +family help preserves existing usage and lists declared operations with partial +coverage marked; detailed help explains declared paths or reports unavailable detail. No boxes, icons, animated output, automatic pager or interactive selection. + +Design variance 2/10; motion 1/10 (none); density D8 with a compact entry page. +Terminal font/colors remain user-owned. Concept-image generation is inapplicable +to this text-only utility surface. Plain and redirected output carry all meaning. + +Root target: at most 28 logical lines, preferably at most 80 columns. Use short +purpose groups rather than every command's operands. Expose setup/start/status, +doctor/logs/usage, core family names, full reference, command help and JSON +capability discovery. Full reference may be long and retains existing details. +Do not truncate command tokens; allow natural wrapping rather than terminal probes +or a new width/layout dependency. Narrow terminal verification reads real output. + +Magpie evidence at the pinned source: main.go lines 31-103 groups examples with +concrete effects, providers_cli.go line 25 has local usage, model_cli.go line 99 +recognizes local help, and groups_cli.go line 226 suggests similar model names. +Magpie does not prove a compact root or uniform command typo recovery; those are +our design decisions. No source is copied from Magpie. + +UX explorer: `01a100e1-f5e8-7080-85cb-e2ab8de5093d`. +Parser explorer: `01a100e1-f53a-78c1-be32-066cb3e5d4f1`. +Accepted findings: nested path loss, provider self-loop, complete-reference escape, +ordinary help-valued operands, unknown-root preflight, incomplete capabilities. +Excluded: #6483 config flags-only defaults, health parser policy, missing-home +bootstrap changes, and new client support. An incomplete help declaration means +"no detailed help available", not "the executable command does not exist". + +Representative desired journey: + +```text +ocx --help + -> ocx help models + -> ocx help models context + -> ocx models context status +``` + +Failure journey: `ocx help modles` offers `ocx help models`, never invokes models. +Static help never reads user configuration to personalize recommendations. diff --git a/devlog/_fin/261003_cli_help_ux/010_help_foundation.md b/devlog/_fin/261003_cli_help_ux/010_help_foundation.md new file mode 100644 index 00000000000..9e2008a08de --- /dev/null +++ b/devlog/_fin/261003_cli_help_ux/010_help_foundation.md @@ -0,0 +1,87 @@ +# wp1: Explicit help paths and complete reference + +Dependency: wp0 audited roadmap. Delivery: first PR against dev. + +## Exact file map and transformations + +- MODIFY `src/cli/root.ts`: retain `helpTarget?: string` for existing consumers; + add optional `helpPath?: string[]` for multi-token paths and `helpAll?: boolean`. + Creation is parseCliHead; runCli consumes them. No persistence/serialization. + Ordinary command args stay byte-for-byte intact. Derive explicit help prefix + before `--`; bare `help` is recognized only at root or immediately after the root + command, never as a later value such as `alias set demo help`. Prefer flags --help/-h for nested help. + Preserve `ocx command help`; exact `--` terminates head help scanning. +- MODIFY `src/cli/help.ts`: rename existing complete template to printFullUsage; + printUsage continues to call it in this layer. Recognize full-reference request + through runCli before rendering a target. Keep printSubcommandUsage(string) + compatible; accept a path through an additional argument or exported renderer. +- NEW `src/cli/help-catalog.ts`: pure resolver importing registry and capabilities + only. Return a discriminated result for top-level entry, declared capability, + declared-prefix group, curated context topic or unavailable path. Resolve exact + registry aliases first, canonicalizing only their nested path. Hidden commands + remain callable under existing explicit help behavior but never enter discovery. +- NEW `src/cli/help-models-context.ts`: export MODELS_CONTEXT_USAGE with the exact + existing context grammar and static explanation/examples for status/value/provider/all. +- MODIFY `src/cli/models-runtime.ts`: replace its inline context usage row with + `${MODELS_CONTEXT_USAGE}` and import the pure constant. Runtime parsing unchanged. +- MODIFY `tests/cli/cli-head.test.ts`: classification and argv-preservation cases. +- NEW `tests/cli/cli-help-paths.test.ts`: nested resolution, alias paths, unsupported + detail distinction, full-reference forms and capability payload preservation. +- MODIFY `scripts/test-layout/layout.json` and + `tests/fixtures/test-layout-expected.json`: register new test in cli domain. +- MODIFY `structure/runtime.md`: replace stale CLI help contract/count paragraph + with current pure help owners, early exit and full-reference navigation. +- MODIFY `docs-site/src/content/docs/reference/cli.md`: document full/nested help + and distinguish declared capability metadata from exhaustive command grammar. + +## Interface and before/after sketch + +```diff +- helpTarget: args[1] ++ helpTarget: path[0], ...(path.length > 1 ? { helpPath: path } : {}) +- if (head.helpTarget) printSubcommandUsage(head.helpTarget) ++ if (head.helpAll) printFullUsage() ++ else if (head.helpTarget) printSubcommandUsage(head.helpTarget, head.helpPath) +``` + +New help path is transient argv-derived data. No API route/schema changes. +Capabilities with no operand declaration render `Command: ocx ...`, summary, +known flags/details and a parent-help pointer, not a fabricated complete Usage. +For a valid but undeclared deeper topic, print that detailed help is unavailable +and point to the known parent; never say the runtime command is unsupported. +For models context, use the exact shared usage plus read-only status example. + +## Activation and proof + +- `help --all` and `--help --all`: exit 0, same complete reference, all previous + recovery variants retained. Root default remains unchanged in this PR. +- `help models context` and `models context --help`: same specific text containing + status/value/provider/all grammar. `model context --help` resolves the same topic. +- `help account list`: declared flags/details, no API request and no writes. +- `help account main`: shows declared deeper children; incomplete marker explicit. +- Unknown deeper detail: nonzero help resolution with parent pointer, no false + claim of invalid execution grammar. Ordinary execution bypasses this resolver. +- `alias set demo help`, `config set defaultModel help`: classify as commands; + `claude -- --help`: remain command argv. Test classification, never launch them. +- Empty existing isolated homes and shim fixtures remain unchanged for all help + forms; --json capability payload comparison remains identical. + +Verification: new focused test plus existing cli-head/help/registry/capabilities +suites and `tests/cli/cli-models-runtime-dispatch.test.ts` (the whitespace-prefixed +context usage row stays byte-identical); typecheck; test:changed; structure and skill surface checks; privacy scan; +English docs build. See wp0 evidence for baseline commands already run. + +## B compatibility amendment + +Existing cli-help.test.ts:246 requires `service uninstall --help` and +`codex-shim uninstall --help` to exit0 with safe parent usage, without changing +state. The incomplete capability catalog cannot reject these established forms. +For appended flag help (`ocx ... --help|-h`), resolve the full known +path first; when detail is unavailable but a parent is known, render that parent +successfully. Explicit `ocx help ` remains a strict topic request and reports +unavailable detail with nonzero status. This preserves prior flag-help safety and +keeps the declared context forms identical. Rendering takes a transient optional +`fallbackToParent` option derived by runCli from original argv/head command; it +is not serialized or persisted and never changes command execution. +Add regressions for flagged service/shim uninstall and explicit unavailable +service install. Public/runtime docs must distinguish these two forms. diff --git a/devlog/_fin/261003_cli_help_ux/011_verification.md b/devlog/_fin/261003_cli_help_ux/011_verification.md new file mode 100644 index 00000000000..a48b438e774 --- /dev/null +++ b/devlog/_fin/261003_cli_help_ux/011_verification.md @@ -0,0 +1,34 @@ +# wp1 verification and remaining full-suite failure + +The help foundation is implemented and independently reviewed. Root text remains +byte-identical, nested context help resolves, declared help is honest, and flag +help retains safe parent fallback. The focused regression added first failed on +the old parser (4 failures); the corrected source passes 85 focused tests, plus +one final explicit-detail regression. Independent review covered all 13 changed +files and probed all 74 declared capabilities: PASS, no actionable findings. + +Main checks: typecheck, structure, generated skill surface, privacy and docs build +pass. Layout tests:18 pass. Changed-import graph:1,036 pass,1 skip,0 fail across +42 files. Docs:561 pages,77,926 internal links. Eleven real isolated-home CLI +scenarios confirmed stdout/stderr, exits, equivalent forms, repeat/plain output +and no state writes; QA receipt is retained locally. + +`bun run test` did NOT pass. Its parallel lane reported35,806 pass,107 skip,4 fail; +subsequent serial lanes completed but do not erase those failures. Three failures +in `tests/update/update-restart-lease.test.ts` reproduce both in isolation here +and in an unchanged `git archive` of4b98328dca (4pass/3fail in each). They concern +parent/child service-authority path disagreement and mutation leases. No affected +service production or test file is changed in this PR. + +The fourth failure is EISDIR in the initial snapshot of +`tests/codex-integration/injection-model-suggest-routes.test.ts`, before its tested +request. Test/harness/direct dependencies match the baseline. One diagnostic +isolated run passes (1pass/12assertions); this does not establish its cause or +clear the full-suite failure. No skip, quarantine or retry-as-fix was introduced. +No exact-baseline hosted workflow was returned by the run lookup; do not describe +this as a proven environmental failure or claim broad validation passed. + +CLI scope checks meet wp1 acceptance. PRs stay draft while broader evidence is +unresolved; wp4 owns each published head's CI and any task-regression repair. +Raw logs live in `.tmp/cli-ux/wp1-*`; QA artifacts are under the session's ignored +evidence directory. This record deliberately preserves the failing gate. diff --git a/devlog/_fin/261003_cli_help_ux/020_help_navigation.md b/devlog/_fin/261003_cli_help_ux/020_help_navigation.md new file mode 100644 index 00000000000..e0b0583cea2 --- /dev/null +++ b/devlog/_fin/261003_cli_help_ux/020_help_navigation.md @@ -0,0 +1,112 @@ +# wp2: Compact root and family navigation + +Dependency: wp1 full-reference and path resolver. Delivery: second PR, based on +codex/cli-ux-help-foundation, head codex/cli-ux-navigation. + +## File map and before/after + +- NEW `src/cli/help-navigation.ts`: static presentation groups referencing existing + registry command names, verified examples and pure compact-root rendering. + Data owns ordering/labels only; registry still owns command identity/summary. +- MODIFY `src/cli/help.ts`: + +```diff + export function printUsage(): void { +- printFullUsage(); ++ console.log(renderRootHelp()); + } +``` + +- MODIFY `src/cli/registry.ts`: replace provider's self-referential detail with + concrete syntax/examples for list, presets and pointers to declared topics. + Preserve exact alias entry identity and add canonical-help navigation for stubs. +- MODIFY `src/cli/help-catalog.ts` and renderer as necessary: family help appends + declared child paths/summaries with a label that coverage is partial. No API + route strings in ordinary human help. Existing registry usage/details remain. +- MODIFY `tests/cli/cli-registry.test.ts` and `cli-help.test.ts`: coverage checks + render full reference instead of assuming the root template contains everything; + move export-count assertion to full view. Keep every public command reachable. +- MODIFY `tests/cli/cli-help-paths.test.ts`: move its 92-line/equivalence checks + to explicit full-reference forms; treat92as baseline observation, not a + permanent reference-length assertion. Default root has independent compact + assertions. Keep all nested/context/safety/JSON cases unchanged. +- NEW `tests/cli/cli-help-navigation.test.ts` plus both layout registrations: + compact default agreement, public group validity, no hidden names, no self-loop, + examples resolve and no ANSI/control dependence. +- MODIFY English CLI reference and runtime structure prose in the same PR. + +Root text uses Start here, Common tasks, Explore, More help. At most 28 logical +lines, common rows within 80 columns. Include `ocx help --all`, +`ocx help ` and `ocx capabilities --json` as conspicuous escapes. +No per-machine customization, width probing, color library, pager or prompts. + +## Observable acceptance + +Bare ocx/help/-h/--help agree and remain side-effect-free. Full reference retains +all visible registry commands and pre-existing detailed variants. Provider help +no longer instructs the user to rerun itself. The model alias keeps its name and +links canonical model help. Family children are marked declared, not exhaustive. +Examples are statically checked and manually read at 80 and 40 column terminal +widths; no command token is clipped, and redirected NO_COLOR output is complete. + +Run focused navigation/path/head/help/registry/capabilities tests, typecheck, +test:changed, structure/skill surface/privacy checks and docs build. + +## P revalidation after wp1 + +Previous D: wp1 explicit/full help is implemented; broader full-suite failure is +recorded, draft readiness remains pending in wp4. This layer retains that behavior. + +CLI-UX-02 architect amendments accepted: +- Entry resolution gains transient canonical identity and declared descendants, + created in help-catalog from existing registry/capability data and consumed only + in help.ts. No serialization/deserialization: N/A, process-local presentation. + Preserve exact registry alias usage/details before canonical navigation. +- Curated models-context is a separate topic link, not a fabricated capability row. +- Preserve capability/prefix/context/unavailable resolution and flag-help fallback. +- Full forms remain identical and preserve registry coverage/recovery variants; + 92lines is the observed baseline, not a permanent count that blocks new commands. + Compact root differs and is <=28lines. + Update cli-help-paths root/full checks and rename unknown-root test's "full + banner" wording to "root banner". wp2 retains its old exit1/stdout+stderr + contract; concise stderr-only recovery remains wp3. +- Compact root references registry names/summaries without silently truncating + text.28logical lines is hard;80columns is a readability target. + +Proposed provider-help owner consolidation for reflection: MODIFY +`src/cli/provider.ts` to remove its duplicated PROVIDER_USAGE string and render +`printSubcommandUsage("provider")` in its existing no-args/help branch. Move the +existing command rows/examples into registry provider.details (one static owner), +retaining general preset/custom guidance and removing the self-loop. No command +handler, validation, credentials or execution grammar changes. Add exact human +output parity coverage for bare provider/help provider/provider --help, plus +existing cli-provider regression coverage. The root no-args provider path still +has its existing preflight; do not falsely claim it has the head-help bypass. + +Provider caller completion (architect correction): migrate BOTH consumers of +PROVIDER_USAGE. Successful no-args/help renders registry help to stdout and exits0. +The unknown provider action prints its existing diagnostic, then registry help to +stderr, and exits1. Add an optional transient `write` sink to the existing +printSubcommandUsage options, defaulting to console.log for successful rendering; +the provider error branch supplies console.error. Propagate it on parent fallback. +No persistent or machine schema fields. Add an isolated unknown-provider regression +asserting empty stdout, stderr usage and exit1; do not change command validation. + +B output read: initialcompactroot24lines/max80columns. Main requested the +standard Usage header and concrete logs/usage registry summaries (retain alias +meaning) so common-task rows describe outcomes rather than only alias plumbing. +This is presentation copy within the existing metadata scope. + +C coverage amendment: changed-import testing found the existing complete-client +help invariant in `tests/gui/integrations-invariants.test.ts` still calls compact +printUsage and expects a full command row. MODIFY that test to call +printFullUsage, retaining every client-id and `ocx integration client` assertion. +This is the same complete-reference coverage migration, not reduced coverage or +new client support. Add this exact file to the focused verification commands. + +Hosted C follow-up: PR6500 test2/4 found a subprocess-only help consumer in +`tests/cli/cli-restore-back.test.ts` that import-graph selection did not discover. +MODIFY its help-documents-both-directions case to invoke `help --all` instead of +compact `help`; retain every restore/back assertion. Verify this file explicitly. +This follow-up belongs to the navigation layer and is committed before wp3's +source, with the parent ref advanced to retain a clean dependency chain. diff --git a/devlog/_fin/261003_cli_help_ux/021_verification.md b/devlog/_fin/261003_cli_help_ux/021_verification.md new file mode 100644 index 00000000000..58b8381757f --- /dev/null +++ b/devlog/_fin/261003_cli_help_ux/021_verification.md @@ -0,0 +1,20 @@ +## wp2 Done (implementation scope; PR remains draft) + +Root help is26logical lines/max80columns, with standard usage, common tasks, +registry-backed descriptions and the full-reference escape. Family/alias links, +curated context topic and provider single-owner help are implemented. Both +provider output channels and appended-help fallback remain correct. + +Five meaningful RED tests preceded135focused passes. Changed-import verification +initially exposed a complete-reference test still reading compact help; preserving +all assertions and switching its renderer fixed it. Final:1,097pass/1skip/0fail +across46files. Typecheck, structure, skill surface, privacy, layout18tests and +docs561pages/77,929links pass.18real CLI QA scenarios and a40x24PTY capture confirm +outputs, exits, no ANSI dependency and no state writes. PTY, child and temp home +were closed/removed. Independent reviewer inspected16/16files:PASS. + +The full local suite's prior four failures remain recorded in011; no full-green +or merge-readiness claim. PR6498's current head has23successful checks and8 +workflow-policy skips, including a successful CI aggregate and format gate. +Next direction: consume030 for concise contextual recovery, keeping all wp1/wp2 +behavior. Publication uses ordinary dependent draft PRs; no merge. diff --git a/devlog/_fin/261003_cli_help_ux/030_help_recovery.md b/devlog/_fin/261003_cli_help_ux/030_help_recovery.md new file mode 100644 index 00000000000..c20cdee86ec --- /dev/null +++ b/devlog/_fin/261003_cli_help_ux/030_help_recovery.md @@ -0,0 +1,97 @@ +# wp3: Contextual help recovery + +Dependency: wp2 navigation and wp1 resolver. Delivery: third PR based on +codex/cli-ux-navigation, head codex/cli-ux-recovery. + +## File map and intended diff + +- NEW `src/cli/help-recovery.ts`: pure conservative typo matching over visible + registry names/aliases or the resolver's declared children. Deterministic order, + short bounded input/candidate work, at most three suggestions. Never execute a + suggestion, and never infer supported runtime grammar from absent capabilities. +- MODIFY `src/cli/help.ts`: unavailable help uses a concise diagnostic and parent + or full-reference pointer rather than dumping the entire banner. +- MODIFY `src/cli/root.ts`: detect unknown root command before shim auto-restore; + use `command === "internal" || findCommand(command)` so the deliberately + unregistered internal runner and registered commands keep existing semantics. + Keep internal absent from public discovery and test its classification without + executing an internal operation; do not import dispatch into pure help. + Preserve head kind compatibility or add explicit unknown state only if all + consumers/tests are updated. No lifecycle mutation for invalid root commands. +- MODIFY `src/cli/dispatch.ts`: existing unknown-command exit reuses the renderer: + +```diff +- console.error(`Unknown command: ${command}`); +- printUsage(); ++ printUnknownCommand(command); + return 1; +``` + +- NEW `tests/cli/cli-help-recovery.test.ts` plus both layout registrations: root + and nested typo, unrelated input, hidden-name exclusion, terminal control input, + long input bound, deterministic suggestions, and no automatic command execution. +- MODIFY `tests/cli/cli-help.test.ts`: preserve the unknown-help regression but + replace its obsolete stdout banner assertion with empty stdout, exit 1 and + stderr diagnostic/navigation assertions. +- MODIFY `tests/cli/cli-head.test.ts`/`cli-dispatch.test.ts` only where established + semantics require it; avoid adding bulk cases to already-large dispatch tests. +- MODIFY English CLI reference and runtime structure prose. + +## Error contract and triggers + +`help modles` and `modles` exit 1, suggest `models`, and print fewer than 10 lines. +Diagnostics and recovery guidance use stderr, leaving stdout empty on unresolved +help/unknown commands. This is an intentional human-error output change; existing +machine-readable successful/failed command payloads and command-specific argument +validation remain unchanged. Never echo terminal controls; bound diagnostics. + +Distant typo `ocx help qzxv`: no suggestion, only full-reference navigation. +Nested typo `ocx help account lisst`: suggest `ocx help account list`, never an +unrelated root. Undeclared deeper path `ocx help service install`: say +"No detailed help available" and point to `ocx help service`, never claim the +valid runtime install operation is an unknown command. Unknown root with shim +fixture: no repair or writes. Known root retains existing preflight behavior. + +Run new recovery tests plus prior layer focused coverage, typecheck, +test:changed, structure/skill surface/privacy checks and docs build. A fresh +independent reviewer checks source and terminal evidence before readiness. + +## P revalidation after wp2 (CLI-UX-03) + +Previous D:26line navigation/family/provider help passed affected checks and fresh +review; inherited local full-suite caveat remains in011. Current base6f92cc1c98. + +- MODIFY help-catalog.ts: it owns a pure recovery-candidate projection using its + existing canonicalization/declared descendants. Root names and aliases are + deduplicated by canonical help destination. Nested candidates are immediate + documented child tokens; include the curated models/context topic separately. + Suggestions contain complete metadata-derived destinations, never trailing argv. + Bound the existing parent-prefix search by the maximum declared/curated depth, + not arbitrary user path length; long explicit paths still remain unavailable. +- Matching policy: ASCII command-shaped tokens of3..64characters only; case-fold + for comparison; bounded edit distance with adjacent transposition. Maxdistance1 + below6characters,2otherwise; order bydistance thenname; atmost3canonical targets. + Reject oversized/control-containing tokens from matching; do not truncate them + into apparent valid commands. Recovery paths deeper than8tokens get no matches. +- Diagnostic policy: root echoes only a bounded command-shaped first token after + the existing pure `redactSecretString` confirms it contains no recognizable + secret; otherwise generic Unknown command. Never echo laterargv. Nested errors + keep generic unavailable-detail text plus metadata-derived parent/suggestions. + Reuse src/lib/redact.ts; do not import stateful runtime-api solely for formatting. +- Keep successful appended-help parent fallback BEFORE recovery; preserve its + write sink. Unavailable explicithelp always usesstderr/exit1, regardless of sink. +- Keep CliHead.kind and parseCliHead contract unchanged. runCli's command branch + rejects unknown roots before shimpreflight, explicitly allowing `internal` and + all findCommand roots/aliases/hiddenentries. No dispatchimport in pure modules. + Direct dispatch's unknownroot exit independently calls the same formatter. +- MODIFY cli-help-paths.test.ts in addition to cli-help.test.ts: migrate rootbanner + expectation to empty stdout/exit1/boundedstderr; preserve operand non-disclosure, + context equivalence, parentfallback, provider sink andcapabilityJSON assertions. +- New recovery tests include root/nested/distanttypos, aliasdedup, curatedtopic, + context-local candidates, long/control rootinput, secret-like trailingvalues, + unknownroot shimfixture immutability and directdispatch. Admit valid aliases, + hidden roots andinternal via isolated preflightspy without executing operations. + +Transient candidate fields are created by catalog projection and consumed by the +recovery formatter; no persistence or API serialization. No global hasHelpFlag, +provider-specific diagnostics, Node updater or missing-home bootstrap changes. diff --git a/devlog/_fin/261003_cli_help_ux/031_verification.md b/devlog/_fin/261003_cli_help_ux/031_verification.md new file mode 100644 index 00000000000..bef10e5896b --- /dev/null +++ b/devlog/_fin/261003_cli_help_ux/031_verification.md @@ -0,0 +1,23 @@ +## wp3 Done (implementation scope; PR remains draft) + +Unknown roots and strict unavailable help now emit concise stderr-only recovery +and exit1. Canonical metadata owns suggestions, with bounded matching/depth and +no automatic execution or trailing-operand echo. Unknown roots stop before shim +preflight; registered aliases, hidden roots and internal remain admitted. Existing +successful parent fallback/sink, provider behavior and capability JSON remain. + +Six RED tests preceded146focused passes. Main changed-import check:1,113pass, +1skip,0fail across48files. Typecheck, structure, skill surface, privacy, layout18 +and docs561pages/77,932links pass.24real CLI scenarios confirm output, exit codes, +case/distance/control behavior, equivalent help and no state writes; teardown +verified. Independent review covered14/14files and passed2,483 oracle comparisons, +75 hostile-input cases,15isolatedCLI probes and73preflight admissions. + +The navigation layer's hosted failure was a subprocess-only full-help consumer. +One argv migration preserved all restore assertions; exact-layer snapshot tests +passed5/5 and independent review passed2/2files. Parent navigation advanced to +aaf3672bd0 before this layer's source commit, preserving stack ancestry. + +Local full-suite four-failure limitation remains in011. No full-green/merge-ready +claim. Next direction: finish040 publication/stack maps, inspect every current +head's CI, record the terminal published-draft outcome and close the loop. diff --git a/devlog/_fin/261003_cli_help_ux/040_publication.md b/devlog/_fin/261003_cli_help_ux/040_publication.md new file mode 100644 index 00000000000..b0e9485405d --- /dev/null +++ b/devlog/_fin/261003_cli_help_ux/040_publication.md @@ -0,0 +1,71 @@ +# wp4: Publish and verify the manual stack + +Dependency: three implementation cycles complete. This phase changes only the +unit's evidence/closure documentation unless review or CI reveals a scoped defect. +A defect requiring code returns to its owning work phase and cascades descendants. + +## Publication map + +1. Push codex/cli-ux-help-foundation and create templated PR against dev. +2. Push codex/cli-ux-navigation and create PR against foundation. +3. Push codex/cli-ux-recovery and create PR against navigation. +4. Attach every PR URL to this chat and update each body with exact stack links, + layer-specific behavior, commands/results and any unverified coverage. + +Use .github/PULL_REQUEST_TEMPLATE.md sections unchanged. No native stack +registration, merge, auto-merge, workflow bypass, skip-ci or release. Changes do +not touch gui, so no GUI screenshot is required. Retain exact root/leaf/error +terminal samples for human review. No reference clone or private data is staged. + +## Validation and evidence + +- Verify ancestor relation, per-layer diff and remote head/base for each PR. +- Read required checks for each current head and actual tested SHA/event; missing, + skipped/cancelled or older-head jobs are not passing evidence. +- Address independent review findings and task-caused CI failures in the owning + layer, propagating lower changes upward. Do not mask unrelated baseline errors. +- Full suite is default before review readiness. A genuine resource exception + records focused commands, reason, missing coverage and draft state where needed. +- Refresh required checks after every push. Poll with bounded intervals and retain + run/check identifiers; do not rerun passed checks without a changed reason. +- Write 090_outcome.md with PR URLs, SHAs, verification and remaining limits, then + archive this unit to devlog/_fin after its terminal published outcome is recorded. + +DONE is published, reviewed and validated scope with evidence. No claim of merge +or live installation is part of the result. Goal completion requires all bound +criteria and completed PABCD cycles, not just successful Git commands. + +## P revalidation: resume actual publication + +Draft PRs already exist:6498(help foundation),6500(navigation),6503(recovery). +Reuse them; do not create duplicates. Publication began after each implementation +cycle so its CI could run while the next layer progressed. Complete reciprocal +stack links and layer-specific evidence in all three template bodies and verify +attachments. The current navigation head includes the independently reviewed +restore-help test migration; preserve that parent ancestry. + +Verify remote head/base SHAs, layer-only diffs and ancestor relationships. Read +native-stack membership independently; confirmed empty is manual, unavailable is +unknown. No registration/conversion/dissolution/merge or other repository-policy +changes. Source repairs must land in their owning layer and propagate upward. + +Readiness limitation is concrete, not a resource exemption: the local full suite +had four failures, three reproduced on baseline and one snapshot failure remains +unresolved. All PRs stay draft. Affected-scope and hosted checks are separate +facts and never retroactively make that local run pass. + +Write090_outcome.md as a terminal PUBLISHED_DRAFT outcome with these limitations, +then archive the unit to_fin on the stack tip. A docs-only closure commit creates +a new final head: verify its required CI before final delivery. Record exact final +SHA/run/check evidence in PR bodies and the local delivery receipt after commit; +do not create an endless self-referential documentation-commit cycle. + +DONE for this goal means completed requested draft publication, verified topology, +independent implementation reviews and accounted current-head CI. It does not +mean full-local-suite success, review readiness, merge, release or installation. + +CI exposed a deterministic two-clock-read deadline extension in the unchanged +CLI restart observer.041 specifies a local C1 prerequisite fix and regression, +separate from UX diffs. Publication now includes that small predecessor plus the +three existing UX PRs; preserve their numbers, rebase/cascade owned heads, and +recheck each new head. No retries-as-fix or timeout/assertion relaxation. diff --git a/devlog/_fin/261003_cli_help_ux/041_deadline_prerequisite.md b/devlog/_fin/261003_cli_help_ux/041_deadline_prerequisite.md new file mode 100644 index 00000000000..0c7940c6e65 --- /dev/null +++ b/devlog/_fin/261003_cli_help_ux/041_deadline_prerequisite.md @@ -0,0 +1,45 @@ +# Bounded CI prerequisite: preserve the restart observation deadline + +PR6500 current-head CI run37114638400, test4/4 job111178976551 failed the existing +production-reserve binding assertion in tests/windows/tray-proxy.test.ts:510. +The observer received a deadline1ms later than its caller's deadline. Both source +and test are unchanged from4b98328dca and latest dev7c59baf959. + +## Cause and exact change (C1 local behavior fix) + +src/cli/tray-proxy.ts reobserveRestartReplacement reads Date.now twice: once to +compute remaining budget, then again to reconstruct the absolute end. Advancing +the clock between reads extends the caller's budget. Capture one now value and +use it in both expressions. No timeout increase, retry or assertion weakening. + +```diff +- const windowMs = Math.min(5_000, deadlineAt - Date.now()); ++ const now = Date.now(); ++ const windowMs = Math.min(5_000, deadlineAt - now); + ... +- const end = Date.now() + windowMs; ++ const end = now + windowMs; +``` + +MODIFY tests/windows/tray-proxy.test.ts: add a deterministic regression with a +clock returning1000then1001, caller deadline6000, and an immediate replacement. +Restore Date.now before awaiting the returned promise. Old source passes6001; +fixed source passes6000. Retain all existing reserve/uncertainty tests. +MODIFY structure/runtime.md within600lines: state the single-observation deadline +contract under the existing CLI restart ownership. No new API/config/dependency. + +## Delivery and verification + +Publish as a separate small prerequisite PR from current dev, keeping UX diffs +focused. Rebase the three owned UX branches onto it in dependency order, preserving +all existing PR numbers and changing only the foundation PR's base to the new +prerequisite branch. Use force-with-lease only for rewritten owned heads, and +verify both ancestry and each current remote head/base after publication. + +Main owns serial Git operations in this bound worktree; no parallel branch writer. +Preserve current delivery docs while switching. Run red/green focused regression, +whole tray-proxy tests, typecheck, structure check, privacy check and an independent +source review. Every resulting PR gets current-head hosted CI. Final outcome stays +PUBLISHED_DRAFT with the unrelated four local full-suite failures still recorded. +This necessary CI repair adds one PR to the requested stack, not client support, +a new lifecycle feature, a merge or a release. diff --git a/devlog/_fin/261003_cli_help_ux/090_outcome.md b/devlog/_fin/261003_cli_help_ux/090_outcome.md new file mode 100644 index 00000000000..b2e3e88f608 --- /dev/null +++ b/devlog/_fin/261003_cli_help_ux/090_outcome.md @@ -0,0 +1,51 @@ +# Published CLI UX stack + +Outcome: **PUBLISHED_DRAFT**. The requested command-help UX is implemented and +published as three dependent PRs, with a small CLI deadline prerequisite that +hosted CI exposed. This is completed draft publication, not merge or full local +validation. No client integrations, installed app or live proxy were changed. + +| Order | PR | Change | Base | +| --- | --- | --- | --- | +| 1 | https://github.com/lidge-jun/opencodex/pull/6506 | Capture one timestamp for restart observation deadlines | dev | +| 2 | https://github.com/lidge-jun/opencodex/pull/6498 | Explicit nested help and complete reference | codex/cli-restart-deadline | +| 3 | https://github.com/lidge-jun/opencodex/pull/6500 | Compact root and family navigation | codex/cli-ux-help-foundation | +| 4 | https://github.com/lidge-jun/opencodex/pull/6503 | Bounded typo guidance and early unknown-command rejection | codex/cli-ux-navigation | + +Root help changed from92to26logical lines; the complete reference remains at +`ocx help --all`. Nested topics, canonical alias navigation, provider-help parity +and concise stderr recovery are covered by regression and real CLI evidence. +No suggestions execute automatically; existing machine capability JSON remains. + +## Verification + +- Help foundation:85focused passes plus final explicit-detail regression; changed + graph1,036pass/1skip/0fail;11real CLI scenarios; independent reviewPASS. +- Navigation:135focused passes; changed graph1,097pass/1skip/0fail after preserving + and migrating full-reference assertions;18CLI scenarios and40x24PTY capture; + independent reviewPASS. Hosted subprocess restore-help migration passed5tests + in an exact navigation snapshot and received independent review. +- Recovery:146focused passes; changed graph1,113pass/1skip/0fail;24CLI scenarios; + independent reviewPASS with2,483oracle comparisons,75hostile cases,15CLI probes + and73preflight admissions. +- Deadline prerequisite: deterministic clock-step RED/GREEN;43tests pass, + 117assertions; independent reviewPASS and five boundary probes. +- Typecheck, structure, generated skill surface, privacy, layout guards and + documentation builds passed for their recorded implementation snapshots. +- `restack-range-diff.txt` in ignored evidence shows every UX commit is + patch-equivalent after restacking. The final combined tree and each current + remote head are checked again after this docs-only closure commit. + +## Limits retained + +The local full-suite run did not pass: four failures were recorded in011. +Three restart-lease failures reproduced on untouched4b98328dca; one initial +snapshot EISDIR remains unresolved despite a passing isolated diagnostic. Neither +later focused checks nor Linux hosted checks retroactively turn that run green. +All four PRs remain draft. Optional platform jobs skipped by workflow policy are +not described as passing platform validation. No merge/release/install occurred. + +Exact final SHAs, tested merge SHAs, workflow events, run attempts and check states +are recorded after the closure commit in the PR bodies and the ignored final +receipt, avoiding a self-referential commit cycle. This unit moves from_plan to_fin +for the terminal published-draft outcome; earlier plan-path references are history. diff --git a/devlog/_fin/261003_dashboard_scroll_gap/000_plan.md b/devlog/_fin/261003_dashboard_scroll_gap/000_plan.md new file mode 100644 index 00000000000..6a249d05669 --- /dev/null +++ b/devlog/_fin/261003_dashboard_scroll_gap/000_plan.md @@ -0,0 +1,38 @@ +# Shared dashboard scroll boundary + +Codex and Claude account pages can keep scrolling after their visible content ends, moving the sidebar upward and exposing a blank lower area. Preserve the existing page layout while giving normal dashboard pages one document scroller. This single C2 work-phase includes repair, rendered regression checks, and a normal PR merged into dev. + +## Loop specification + +- Archetype: satisfy-spec bug repair. Trigger: user screenshot and explicit request to fix with Sol subagents and merge. +- Goal: no second outer scroll or blank strip below the sidebar after reaching the end of account pages. +- Non-goals: provider/settings behavior, dependency updates, release/deployment, installed-app replacement, wholesale fixed-shell redesign. +- Verifier: real Chrome measurements and screenshots; maintained built-CSS browser regression; GUI suite/lint/build; repository typecheck, structure check and required current-head CI. Browser script is new and must first fail on unchanged CSS. Existing GUI tests do not calculate layout. +- Stop: merged PR, recorded passing required CI, rendered evidence and honest native-runtime coverage limits. +- Memory artifact: this unit, ignored .tmp/scroll-gap evidence, durable .codexclaw goalplan. +- Outcomes: DONE only after all criteria; unresolved external permission/runtime failures remain unmet. No invented budget exhaustion. +- Escalation: broader shell redesign or unavailable merge authority; main owns decisions and git. No token/cost/time bound was supplied. Tool/credential scope is local GUI, existing browser tooling, and repository GitHub PR/CI/merge authority. + +## Evidence and hypotheses + +H1: body overflow-axis coupling creates two vertical scrollers. Falsifier: body is not scrollable or removing its horizontal scroll container does not remove the defect. +H2: page bottom padding/min-height is excessive. Falsifier: existing content bottom aligns with viewport after changing only body scroll ownership. +H3: sidebar/background painting alone fails. Falsifier: measured sidebar rectangle itself leaves the viewport. + +Chrome on the actual Vite GUI, /#codex-set/multiauth at 1280x800, gave body scrollTop=704, then window scrollY=352 after a second downward wheel. Sidebar top=-352/bottom=448; .app bottom=448.375. Changing only body overflow-x to clip gave body scrollTop=0, window scrollY=704, sidebar top=0/bottom=800 and .app bottom=800.375. This rules out H2/H3 as primary causes. Generic tall shell without an escaped absolute descendant did not reproduce the outer overflow; regression must drive the second scroll. + +## Consultation + +V1 multi_agent_v1 transport; architect 01a0ff1b-efd3-7c61-bcae-715a83b10fda requested gpt-6.1-sol. D1 accepted: body-only overflow clipping; D2 accepted: keep document scrolling and existing height model; D3 accepted: preserve titlebar/mobile/Combos relationships; D4 accepted: rendered geometry before/after, not CSS strings as visual proof. Same-architect reflection: ALIGNED with D1-D4; accepted clarifications: fixture is mechanism coverage, actual Codex and Claude routes are mandatory; production mobile drawer effect must lock/restore without scroll jumps. Independent A follows reflection. + +## Scope and delivery + +See 010_scroll_boundary.md for exact changes. Existing source owners and test patterns reused; no runtime abstraction or dependency needed. One ordinary PR targets dev, no native stack. Full root suite is disproportionate for a CSS-only runtime delta; focused GUI checks and browser regression run locally, broad runtime suite stays with required hosted CI. Screenshot evidence is uploaded to pr-assets, never committed on the feature branch. + +Toggle proof also reproduced Claude: body=811, window=537, sidebar bottom=263 before; body=0, window=811, sidebar bottom=800 with clip; removing override restored the same 537px gap. Built unchanged GUI successfully; baseline bundle retained in ignored scratch. + +Mobile amendment: body-only lock allowed document wheel movement after clipping. A narrow-screen html:has(.sidebar.open) overflow-y:hidden rule repaired it in the actual App (open=482, after wheel=482, close restores prior 500). Keep the existing body effect; root lock follows the real open class. Architect D3 recheck completed ALIGNED after stable-anchor measurement. + +A review accepted the browser-harness correction: built entry CSS alone misses the lazy App chunk, so tests must load both in production order. Architect D3 final reflection ALIGNED: actual anchor top stayed 396.4375px before/open/wheel/close; scrollY adjusted with reflow. Existing scrollbar-width horizontal shift is outside this vertical gap repair. All D1-D4 remain aligned. Baseline focused titlebar/viewport tests: 15 pass, 0 fail. + +B/C evidence so far: actual Codex/Claude browser and desktop-UA surfaces at 1280/1024/768/390/320: 20/20 geometry cases passed. Actual mobile Escape/navigation/resize dismissal and wheel/touch locks passed. Native WKWebView offline probe reproduced hidden -> clip -> hidden and passed 16 observations; packaged Tauri itself was not launched. GUI suite: 2756 pass/0 fail; root typecheck and GUI build passed. diff --git a/devlog/_fin/261003_dashboard_scroll_gap/010_scroll_boundary.md b/devlog/_fin/261003_dashboard_scroll_gap/010_scroll_boundary.md new file mode 100644 index 00000000000..d3070f2b8cd --- /dev/null +++ b/devlog/_fin/261003_dashboard_scroll_gap/010_scroll_boundary.md @@ -0,0 +1,20 @@ +# WP1: shared document scroll ownership + +Dependency: existing shell in gui/src/styles.css, App.tsx and app-titlebar.css. No new types, enums or enforcement layer. + +1. MODIFY gui/src/styles.css: change only body overflow-x from hidden to clip. Keep html horizontal guard, html/body/root heights, sidebar sticky/100dvh and main sizing. Add a short rationale for avoiding a second vertical scroller. Do not alter page padding. In the existing max-width:760px media block, add html:has(.sidebar.open) { overflow-y: hidden; } so the production drawer also locks the document scroller. Class removal restores the normal root overflow automatically; keep App effect and cleanup unchanged. +2. NEW gui/tests/shared-scroll-browser.ts: reuse the standalone isolated Chromium/CDP pattern from sidebar-version-browser.ts. Read both entry CSS and lazy App CSS in production order (titlebar, collapsed-sidebar and mobile Combos rules are in App CSS); record their hashes; render representative Codex/Claude account content with an absolute descendant outside a positioned ancestor to drive outer overflow. Test short/long content, browser and desktop chrome, normal/collapsed rail, desktop/tablet/mobile widths, themes, and final-control reachability. Scroll body and window successively; fail on blank beyond app or displaced desktop rail. Include mobile drawer lock/restore and Combos bounds mandatory. Write only ignored artifacts; expose deliberate old-CSS baseline mode or run against a baseline build for red proof. +3. MODIFY gui/tests/viewport-scroll-caps.test.ts: extend the existing effective-declaration guards for body clipping and mobile root lock; this default-CI source check complements rendered tests. MODIFY gui/package.json: add test:shared-scroll script for the standalone browser regression. It is explicit execution, not part of bun test tests discovery. +4. MODIFY owning structure GUI/layout documentation: state normal pages use document scrolling and clipping must not create a body scroller. Review dashboard/desktop owners; no provider/API doc changes. +5. MODIFY unit 090_summary.md at completion and move unit to _fin. Publish reviewed screenshot through pr-assets and open normal dev PR with full template. + +## Acceptance activation + +- Long Codex and Claude account content: two downward scroll gestures (or equivalent body+document scrolling) end with sidebar top approximately 0 and bottom viewport height; no blank tail beyond app. Toggle old body hidden on/off restores/removes defect. +- Short content: no vertical overflow introduced; normal bottom whitespace is not a failure. +- Browser/desktop chrome and both themes: no titlebar displacement or rail discontinuity. +- 1280/1024/768 wide views and 390/320 mobile: horizontal clipping retained, final control reachable. Fixed drawer remains viewport-bound; drive the actual App drawer effect, confirm wheel/touch document lock and restoration with stable content viewport position and restored document position after close; test Escape, navigation dismissal and resize past 760px. Recheck actual Codex and Claude routes after the patch. +- Combos: existing explicit viewport scroller remains within its available height; no outer blank tail. +- GUI suite/lint/build, root typecheck, structure gate and required current-head CI succeed. Standalone browser regression is run explicitly. Do not represent emulated desktop classes as packaged WKWebView execution. + +Test implementation is delegated to a Sol worker owning only gui/tests/shared-scroll-browser.ts, gui/tests/viewport-scroll-caps.test.ts and gui/package.json after A passes. Main owns CSS, docs, browser diagnosis, commits, PR and merge. Independent Sol reviewer owns read-only A and separate fresh final review. diff --git a/devlog/_fin/261003_dashboard_scroll_gap/090_summary.md b/devlog/_fin/261003_dashboard_scroll_gap/090_summary.md new file mode 100644 index 00000000000..530f5967ec6 --- /dev/null +++ b/devlog/_fin/261003_dashboard_scroll_gap/090_summary.md @@ -0,0 +1,20 @@ +# Scroll repair verification + +The shared shell now has one document scroller. Body horizontal clipping no longer creates a second vertical scrollport; the mobile drawer locks the document while open. The runtime delta is two CSS rules, with no settings/API changes. + +The actual Codex and Claude pages reproduced the blank tail after exhausting body scrolling and then scrolling the document. Changing only body overflow removed the gap; restoring the previous rule restored it. A native WKWebView fixture independently reproduced the same mechanism, including the effect of escaped screen-reader-only descendants. Padding and background painting were not the primary cause. + +## Evidence + +- GUI suite: 2,756 passed, 0 failed across 316 files; root typecheck, GUI lint and production build passed. +- Actual account routes: 20/20 combinations passed across 1280, 1024, 768, 390 and 320 widths, browser and desktop user-agent mounts. +- Actual mobile drawer: wheel/touch lock plus Escape, navigation and resize dismissal passed; vertical anchor stayed in place through open/close. +- Built-CSS standalone regression: old bundle 34/62 failures; patched bundle 0/62. Entry and lazy App CSS are loaded in production order and hashed. It covers themes, short/long content, collapsed navigation, mobile drawer lock/restore and synthetic Combos containment. +- Native macOS WKWebView: 16 observations passed with hidden/clip/hidden reversal. Probe window/process were closed. +- Independent final Sol review: PASS, no blocking findings. Plan review also passed after requiring the lazy App stylesheet in the browser harness. + +Raw geometry and screenshots stay in ignored scratch; the PR uses only synthetic, non-account screenshots on pr-assets. The source guard runs under ordinary GUI tests; the rendered regression is explicitly invoked with test:shared-scroll. + +## Limits and integration + +Packaged Tauri installation, older WebKit and zoom are not verified. Real mobile Combos keeps its existing natural page layout; synthetic containment does not prove that real page is viewport-sized. The broad root runtime suite is deferred to hosted CI because this is a shared-CSS repair; the full GUI suite and direct renderer checks ran locally. Required current-head CI and the merge outcome are recorded in the PR and goal ledger before task completion. No release, deployment or app installation is part of this change. diff --git a/devlog/_fin/261003_release_native_accounts/000_public_scope.md b/devlog/_fin/261003_release_native_accounts/000_public_scope.md new file mode 100644 index 00000000000..4454b8331f7 --- /dev/null +++ b/devlog/_fin/261003_release_native_accounts/000_public_scope.md @@ -0,0 +1,46 @@ +# Native-account carry publication + +This record preserves three public proposals used in the integration train. It records +source provenance and review order; it does not claim that the proposals have +landed or passed integration checks. + +| Public source | Proposed behavior | Observed source head | +| --- | --- | --- | +| [#6496](https://github.com/lidge-jun/opencodex/pull/6496) | Explain revoked-session quota failures while retaining the last-known plan. | `8b645854fcae802c93c516a10245f9c3b7eabb76` | +| [#6507](https://github.com/lidge-jun/opencodex/pull/6507) | Keep stored native-main credentials in Pool health on translated Claude turns. | `7ffd1a856957c320d139ff262b4adaf4dd3da07b` | +| [#6505](https://github.com/lidge-jun/opencodex/pull/6505) | Try the next combo target when the current ChatGPT plan cannot use a model. | `ce2c1a60fd6934ee84b1c52d4392a6128911d914` | + +All three proposals were open against `dev` when inspected on 2026-10-03. +Their author is @vadymhimself. Carries preserve source authorship and coauthor +trailers. Source heads and reviews must be rechecked before adoption. + +Review quota diagnostics first, then stored-main ownership and refresh, then +combo refusal behavior. Each coherent change receives its own regression checks, +independent security review where applicable, and applicable CI at the exact PR +head. The integration coordinator owns merging and final integrated verification. + +Unpublished security analysis and implementation plans remain in ignored scratch +space under the repository's security-working-notes policy. This public record +contains only scope already disclosed by the linked proposals. + +## Publication outcome — 2026-10-04 + +Preparation is recorded as three public carry pull requests: + +| Carry | Published scope | Local focused verification | +| --- | --- | --- | +| [#6515](https://github.com/lidge-jun/opencodex/pull/6515) | Revoked-session quota diagnostics, retained plan, and causal reauth attribution. | 750 tests passed at the corrected implementation checkpoint. | +| [#6523](https://github.com/lidge-jun/opencodex/pull/6523) | Stored-main credential provenance, scoped grant refusal, preview read fences and alternate-refresh cancellation. | 934 tests passed across 20 files; 51 hard-lock read-guard cases also passed. | +| [#6527](https://github.com/lidge-jun/opencodex/pull/6527) | Plan/model combo fallback with bounded evidence through HTTP and SSE error projection. | 374 tests passed across 11 files at `af350a1924ee55ddc60fe9fa04289d1aa494f437`; [exact-head CI](https://github.com/lidge-jun/opencodex/actions/runs/37146395644) passed. | + +The carry commits and descriptions retain @vadymhimself's authorship credit and +source commit references. Each pull request records independent review and its +applicable CI; current-head checks remain authoritative after further commits. +The full local suite was deferred because concurrent release worktrees shared the +host. Focused regressions, typecheck, privacy, structure and relevant documentation +checks ran locally; the coordinator owns final integrated verification. + +This closes publication preparation. Integration, source-PR disposition and release +remain coordinator-owned. Real provider sessions and packaged application behavior +remain outside this lane's evidence. Detailed investigation and security review +working notes remain in ignored scratch space. diff --git a/devlog/_plan/261003_lane_e_recovery/000_plan.md b/devlog/_plan/261003_lane_e_recovery/000_plan.md new file mode 100644 index 00000000000..7d704addf4f --- /dev/null +++ b/devlog/_plan/261003_lane_e_recovery/000_plan.md @@ -0,0 +1,17 @@ +# Lane E recovery + +Preserve the complete Claude subagent-force and Remote Link patches on train 261003, then repair demonstrated routing and discovery defects. Existing explicit gateway selectors must survive the generated-agent compatibility fallback; legacy bare Claude fallback remains supported. + +Satisfy-spec, one cohesive recovery cycle. Trigger: audited coordinator roadmap 995dee54a0 and explicit Lane E implementation handoff. Outcome: local reviewed commits, focused regression results, and inspected fixture-backed browser evidence. Out of scope: publishing, CI dispatch, merge, install, broad tests/typechecks/builds, live accounts/configuration, deferred issues 2511/4198/4961. No token or wall-clock budget was supplied; bounded explicit batches contain at most eight files and GUI tests use isolation. Existing dependency trees may be linked after lockfile identity verification. + +Verification: focused root Claude launch/inbound/force/API/CLI tests; focused GUI force/Remote Link/locale tests; structure, test-layout, file-size and generated capability checks. Browser drives the production pages against an isolated fixture on port 4176, including failure/retry, force toggle and keyboard/fingerprint states. Broad typechecks/builds and final cross-platform CI belong to coordinator. Stop after repairs and carries are committed, focused checks pass, independent findings are resolved, and evidence is recorded. A real unavailable dependency or unresolvable behavior remains an explicit incomplete outcome, never passing evidence. + +Detailed source assessment, consultation, threat-boundary notes, test logs and screenshots stay in ignored `.tmp/train-recovery/`. This unit links the single implementation plan in `010_recovery.md`. Runtime source contracts are recorded in `structure/subagents.md`, `structure/runtime.md`, and affected Claude/Remote Link documentation. No new permission enforcement or public endpoint is introduced. + +Architect consultation: Dalton, agent 01a10041-cb6d-78e2-8b75-9433566765d7. Accepted P1 explicit-selector precedence independent of saved config; P2 bare-native fallback; P3 authenticated gateway catalog reuse; P4 fail closed on unavailable discovery. P1 intentionally changes explicit-alias precedence even with force unset: explicit wire routing wins; bare native fallback is unchanged. Configured force targets are roster entries resolving to gateway aliases, including native OpenAI entries. Bare native Claude overrides remain ambiguous with legacy fallback; do not guess from a user-agent version. Document that generated roster overrides require explicit gateway aliases. Main will obtain same-architect reflection and independent plan audit before implementation. + +Same-architect reflection on these concrete files: ALIGNED; P1 -> step 2, P2 -> step 2 and documented native limitation, P3/P4 -> step 3. No remaining architectural gaps in this repair scope. + +P2 amendment under review: claudeCodeAlias preserves bare Anthropic identifiers. Configured Anthropic force needs native alias encoding plus native passthrough restoration, not a disclaimer of incomplete support. Literal external bare overrides retain legacy ambiguity; the configured feature will work for native targets. + +Amended same-architect reflection: ALIGNED. Force-only aliasForNative wrapping preserves bare Claude context metadata; precedence precedes canonical native-alias restoration. Existing native credential/admission gates stay unchanged; provider aliases stay routed. Required regression negatives: no credentials, disabled passthrough, ordinary provider alias; preserve Fable tests and count-tokens parity. Actual installed CLI acceptance remains an explicitly reported live limit. diff --git a/devlog/_plan/261003_lane_e_recovery/010_recovery.md b/devlog/_plan/261003_lane_e_recovery/010_recovery.md new file mode 100644 index 00000000000..78f72a83140 --- /dev/null +++ b/devlog/_plan/261003_lane_e_recovery/010_recovery.md @@ -0,0 +1,15 @@ +# Carry, repair and verify + +Depends on: coordinator roadmap 995dee54a0 and the completed Lane E read-only assessment. + +1. Carry 8e2152fa30 with a corrected multiline commit message and original authorship; carry 3fec4a4e31. Preserve every sub.force and link.setup/discovery locale key semantically. Do not modify other lanes. +2. MODIFY src/claude/inbound-model-options.ts: replace equality against mutable config with existing gateway alias recognition. Return the original explicit selector when a legacy directive exists; retain the directive for absent/unrecognized/bare-native selectors. MODIFY both call sites in src/server/claude-messages.ts when helper signature changes. Configured Anthropic force entries currently resolve to bare Claude selectors: encode those force-only values using the existing native alias grammar, then generalize the existing Fable native-alias restoration in both handlers so caller-credential passthrough keeps the original bare model. Ordinary roster aliases remain unchanged; do not route these through anthropic/provider aliases which alter the credential path. Test real handler passthrough and count_tokens with a fixture upstream. No directive means unchanged main/sidecar routing. Tests use different configured A, wire B and directive C; changed/cleared configuration leaves B authoritative. Current, legacy and escaped aliases plus 1M markers, malformed/no directive, FORCE=0 and legacy fallback are covered. +3. MODIFY src/claude/gateway-cache.ts: separate authenticated bounded catalog acquisition from cache writing; preserve refresh wrapper compatibility. MODIFY src/cli/claude.ts: obtain fresh connected gateway rows before force env assembly, reuse them for cache writing, skip automatic force if discovery failed; never treat context-window keys as exposure evidence. MODIFY src/claude/subagent-model.ts for an explicit exposure input union distinguishing roster entries from wire selectors; every API caller wraps roster candidates as entries, while connected launch passes freshly fetched selectors. Compare canonical decoded identities with 1M markers stripped. Native force alias encoding happens only after successful exposure validation. Reuse alias decoders, no hub DTO/API/auth expansion. Exposed entries without window metadata qualify; stale cached entries and failed/malformed/unauthorized discovery do not. New field chain: launch-only dependency input -> in-memory helper -> environment builder, no persisted or public serialization. +4. MODIFY existing force regression files and gateway/connected-launch tests; NEW focused test file only if existing file caps demand it, with both root layout maps registered. Root baseline inbound and generated agents: 80 pass, 0 fail before carry; new carry verifiers cannot execute before their files arrive. Exact planned commands use explicit existing/new file arguments. Main owns sequential test execution. +5. MODIFY gui/tests/remote-link.test.tsx: add confirmation -> rescan -> fresh confirmation regression; preserve production patch. A bounded inherited executor may own only this file; no git operations or parallel tests. MODIFY existing Claude guides in all locales to replace obsolete hand-edit-only guidance and describe wire alias precedence and native fallback limitation. Synchronize affected structure contracts without duplicating general policy. +6. Drive Subagents and Remote Link in a browser at desktop and narrow/CJK viewports. Use production components/styles and an isolated API fixture, never live user configuration. Save and inspect screenshots under ignored scratch. Exercise loading/empty/discovery failure/retry/manual alias/confirmation, enable/change/clear force, disabled controls and failure rollback, focus traversal and restoration. +7. Fresh independent review of the final delta and targeted checks; fix demonstrated defects only. Commit locally, leave branch clean apart from ignored evidence, record exact commands/results, source anchors, review verdict, screenshots and native/live limits in `.tmp/train-recovery/report.md`. + +Scope/verification boundaries: explicit routing recognition is not authorization; normal provider/admission validation remains in force. Unknown aliases continue to fail through existing routing. Discovery is a read with the existing credential domain. No release, native CLI execution or actual SSH connection is required or claimed by fixture evidence. + +Native acceptance matrix: configured anthropic/claude-* force uses aliasForNative only after checking original-selector exposure and context metadata. Both handlers restore only exact current/legacy native aliases, never provider aliases; caller sk-ant credentials reach the native fixture with bare model, missing credentials and nativePassthrough=false do not. Preserve existing Fable behavior. Wire identity and API exposure do not grant provider credentials or admission. diff --git a/devlog/_plan/261003_lane_e_recovery/090_summary.md b/devlog/_plan/261003_lane_e_recovery/090_summary.md new file mode 100644 index 00000000000..f5dea4487e1 --- /dev/null +++ b/devlog/_plan/261003_lane_e_recovery/090_summary.md @@ -0,0 +1,9 @@ +# Lane E local recovery result + +Both complete issue patches were preserved and carried from their recorded source commits. Follow-up repairs preserve explicit launch selectors across later configuration changes, validate connected targets against fresh gateway exposure, and retain native Claude credential routing through reversible aliases. Remote Link production behavior is preserved, with regression coverage requiring fresh fingerprint confirmation after rescan. Contract-check failures in the carried layout/capability entries and force component were corrected; nine copied-English force catalogs were translated. + +Local verification: routing/native 174 passing tests; launch/client/endpoint 136; isolated GUI 99 before the final force loading refinement; focused force GUI 3 after refinement; layout/capability/skill contracts 53. The final locale and screenshot evidence is retained in ignored task scratch and reported in the chat handoff. Structure, file-size, generated capability surface, i18n lint and privacy checks passed. Earlier failing evidence is retained with its repair, not treated as an environmental exemption. + +The inherited independent code reviewer found no demonstrated correctness issue in the reviewed repair. Browser QA drives production page components and stylesheet with fixture data, covering force enable/change/clear/failure rollback and Remote Link discovery/retry/manual alias/fingerprint invalidation/keyboard behavior. It does not prove live Claude subprocess acceptance, live SSH, native Windows behavior or the complete dashboard shell. + +This lane is local-only. Integration, broad typechecks, GUI/docs builds and final cross-platform CI remain coordinator-owned. The unit remains in _plan until that integration outcome is recorded. Deferred policy issues and product holds remain outside this work. diff --git a/devlog/_plan/261003_ollama_commentary_replay/000_plan.md b/devlog/_plan/261003_ollama_commentary_replay/000_plan.md new file mode 100644 index 00000000000..a2cbdba3627 --- /dev/null +++ b/devlog/_plan/261003_ollama_commentary_replay/000_plan.md @@ -0,0 +1,25 @@ +# Preserve native Ollama tool batches through assistant commentary + +Ollama replay currently settles an open tool batch when assistant commentary appears, so the later genuine result becomes orphaned. Carry #6509's local batch deferral and add adversarial validation/immutability coverage. This is independent of the Antigravity PR #6514. + +Reader: release coordinator choosing an independently verified head for integration. + +- Archetype/trigger/goal: satisfy-spec Lane C 020, C3 adapter contract. Preserve genuine results next to their original calls while retaining strict validation. +- Non-goals: merge/release, account/config/service changes, OpenAI adapter changes, cross-batch result recovery, shared registry normalization, new paid resources. +- Verifiers: existing native request/parser/reasoning/structured-output tests, shared tool-conformance tests, new malformed/history/frozen-input cases; typecheck/privacy/structure/file-size/docs build and applicable exact-head PR CI. Baseline seven Ollama/conformance files passed 87 tests; commands and logs in ignored scratch. Full local suite exception: concurrent worktrees and import-graph expansion to 1,490 files; prior broad run failed in documented unrelated test-home fixtures, not claimed passing. +- Stop/outcomes: local implementation cycle closes after focused proof and independent code/security review; c-4 retains actual successful CI and complete handoff. DONE requires all lane criteria; unavailable native service or unrelated harness coverage remains explicit, never fabricated. +- Artifacts: this unit and .tmp/release-stabilization/report.md. Escalate only new scope/authority or valid unresolved blockers; no user token/time cap. Bounded commands, inherited leaf subagents, no account mutations or new paid resources. + +Previous D: Antigravity local cycle closed at f9e1e6b492 with focused tests/gates/docs and code/security PASS; #6514 current-head CI remains c-4. Continue the prewritten independent Ollama 020 direction. Coordinator ROADMAP LOCKED remains in force. Its final integrated parallel regression is coordinator-owned; this slice may publish promptly after scoped review/tests. Lane B exclusively owns shared registry duplicate normalization; this slice needs no new registry entries because the existing native test file is uncapped. + +Source #6509 de5bb1f215c613670e03585e918a344c510ba97a remains open; carry all four source commits with provenance and Co-authored-by: potota90 <85318310+adtumk@users.noreply.github.com>. Use own codex/release-261003-c-ollama branch from fresh dev, ordinary PR target dev, no native stack registration. Preserve source author branch and PR. + +Prior roadmap architect Hume 01a1020f-0150-7a62-ba3e-e2b7bad4494a proposed D5/D6 and reflected ALIGNED; whole-roadmap independent audit PASS. This cycle revalidates the exact 010 source diff and specified new tests at current dev. No change in module responsibility or interface: unresolvedCount is request-local pending-batch state. Independent A recheck precedes B. + +File map: MODIFY src/adapters/ollama-native.ts; tests/providers/ollama/ollama-native.test.ts; docs-site/src/content/docs/reference/adapters.md; structure/providers/chat-compat.md. ADD this numbered unit documentation. Detailed source diff and acceptance scenarios are in 010_replay.md. No new public field, stored schema, endpoint or dependency. Runtime flows and all adjacent adapter owning documents are reviewed; only the Ollama replay paragraph changes. + +Revalidation: git apply --check on the source four-file diff exits 0 against current dev. Existing adapter source remains byte-identical to the original roadmap baseline. Baseline counts are 26 native + 29 parser + 6 v4 + 7 reasoning + 10 structured-output + 8 tool-conformance + 1 buffered-conformance = 87 pass, 0 fail, each file isolated. + +Same-architect D5/D6 reflection ALIGNED; corrected stale self-reference and removed unused new-file alternative. Exact additional adversarial test block is recorded in 010. Coordinator provided common registry normalization 29a9e91f400d24ac746a18dfe3af357f5da6dec3 for adoption with -x provenance; no auth runtime carry. + +B carried all four #6509 commits with original authors and -x provenance. Additional negative/frozen-input tests failed five cases on the original adapter (31 pass/5 fail) and all42 native tests passed after carry. No new fixture registry entry or routing/account change. Next C validates related native/conformance suites, typecheck/privacy/structure/docs and independent security review. diff --git a/devlog/_plan/261003_ollama_commentary_replay/010_replay.md b/devlog/_plan/261003_ollama_commentary_replay/010_replay.md new file mode 100644 index 00000000000..c9525fbf01f --- /dev/null +++ b/devlog/_plan/261003_ollama_commentary_replay/010_replay.md @@ -0,0 +1,397 @@ +# Keep unresolved native batches open across commentary + +Depends on: roadmap/unlock only; runtime independent of the Antigravity slice. Carry #6509 at de5bb1f215c613670e03585e918a344c510ba97a on a separate own branch from current dev. + +MODIFY the four files in the exact proposed source diff below, adapting only current-base conflicts. PendingToolBatch.unresolvedCount is created from wireCalls.length, decremented only after validated result attachment and consumed only by the no-new-call assistant deferral branch. It is request-local internal state: serialization/deserialization N/A; no persisted or public API field. + +The new branch activates with an open batch, unresolvedCount > 0, assistant role and no extracted calls. It defers assistant text/thinking until flushPending emits genuine results in original call order and then releaseDeferred emits conversation arrival order. New tool-call batches and EOF settle missing results with existing unknown markers. Fully resolved batches flush before normal assistant turns. Validation remains before decrement: unknown/duplicate/name/namespace results reject; invalid/reused call IDs retain their existing rules. No parsed input mutation. + +## Negative verification delta beyond source PR + +MODIFY tests/providers/ollama/ollama-native.test.ts (existing uncapped file; no new registry entries): construct batches with commentary interleaved before orphan IDs, duplicate result IDs, mismatched tool names and mismatched namespaces; each must throw the existing error. Add old-batch result after a new tool-call batch (must reject), unresolved EOF (must emit exactly one unknown result before commentary), fully resolved result then commentary (must not defer past subsequent conversation), reverse-arrival results in a multi-call batch (must output call order), and deep-frozen parsed context with nested content/call argument objects (must build unchanged). Assert output payload order and values rather than internal counters. Keep all existing malformed-input tests. + +Commands planned, NOT RUN under initial gate: bun test tests/providers/ollama/ollama-native.test.ts; native-parser/native-v4/native-reasoning-wire/native-structured-output files in separate Bun processes; tests/adapters/adapter-tool-conformance.test.ts and adapter-buffered-tool-conformance.test.ts; typecheck/privacy/structure/layout/file-size/test:changed gates. Document full-local-suite exception for concurrent worktrees. No live Ollama service is assumed; absence is an explicit evidence limit. + +Review all src/adapters ownership docs; preserve OpenAI behavior and touch only the Ollama paragraph in chat-compat. User docs describe observed contract, never promise caller-side physical tool execution. Independent code/security review and applicable exact-head CI are required. Source #6509 stays open. + +Credit: Co-authored-by: potota90 <85318310+adtumk@users.noreply.github.com>. + +## Executable source delta + +```diff +diff --git a/docs-site/src/content/docs/reference/adapters.md b/docs-site/src/content/docs/reference/adapters.md +index a765419710..c616f58a0e 100644 +--- a/docs-site/src/content/docs/reference/adapters.md ++++ b/docs-site/src/content/docs/reference/adapters.md +@@ -118,6 +118,10 @@ be configured on a separately named custom or self-hosted Ollama provider with + is refused rather than mis-sent, and remote image URLs are not fetched. + - **Tools:** declared in Ollama's native shape, streamed tool calls are whole-call records with + object-valued `arguments`, and tool-result replay is paired strictly by call id and tool name. ++ Codex may record assistant commentary before a pending call's results. Text/thinking with no ++ new tool calls is deferred until the batch is settled, so genuine results remain beside their ++ originating calls. A new tool-call batch still settles the preceding one; missing results retain ++ an explicit unknown-status marker, and orphan or duplicate results remain invalid. + `tool_choice: "none"` and `auto` behave normally; **`required` or an exact named choice fails + closed**, because Ollama's `/api/chat` has no `tool_choice` field to enforce it with. + - **Structured output is refused on canonical Ollama Cloud.** Ollama currently documents structured +diff --git a/src/adapters/ollama-native.ts b/src/adapters/ollama-native.ts +index 2f95c5dbdc..b180d1f1d5 100644 +--- a/src/adapters/ollama-native.ts ++++ b/src/adapters/ollama-native.ts +@@ -77,6 +77,7 @@ interface PendingToolCall { + interface PendingToolBatch { + calls: PendingToolCall[]; + byId: Map; ++ unresolvedCount: number; + } + + interface NativeStreamToolCall { +@@ -302,6 +303,12 @@ function assistantTextThinkingAndCalls(message: OcxAssistantMessage): { + }; + } + ++/** ++ * Build native replay messages without mutating the parsed history. Keep tool results ++ * beside their originating batch, deferring intervening conversation until settlement. ++ * Reserve replayed call IDs for response translation and mark missing results explicitly. ++ * @throws When call IDs are invalid or results are orphaned, duplicated, or mismatched. ++ */ + function buildNativeMessages( + parsed: OcxParsedRequest, + reservedToolCallIds: Set, +@@ -323,12 +330,14 @@ function buildNativeMessages( + // early. The openai-chat adapter defers them the same way; refusing the replay killed the turn. + let deferred: OllamaNativeMessage[] = []; + ++ /** Emit held conversation messages in their recorded arrival order after settlement. */ + const releaseDeferred = (): void => { + if (deferred.length === 0) return; + messages.push(...deferred); + deferred = []; + }; + ++ /** Settle the open batch with genuine results or unknown markers, then release deferred messages. */ + const flushPending = (): void => { + if (!pending) return; + for (const call of pending.calls) { +@@ -376,13 +385,14 @@ function buildNativeMessages( + throw new Error(`ollama-native tool result ${message.toolCallId} names the wrong originating tool`); + } + call.result = message; ++ pending.unresolvedCount--; + continue; + } + + // Native Ollama requires the whole assistant tool-call turn followed by its tool results. A + // conversational message that arrives while the batch is still open is held aside instead of + // closing it, so the call keeps its results adjacent; it is released right after the batch +- // flushes. Anything else (a new assistant turn) settles the batch first. ++ // flushes. A new assistant tool-call batch settles the preceding batch first. + if (pending) { + if (message.role === "user" || message.role === "developer") { + const translated = message.role === "user" +@@ -393,6 +403,19 @@ function buildNativeMessages( + : { role: "system", content: translated.content }); + continue; + } ++ if (message.role === "assistant" && pending.unresolvedCount > 0) { ++ const extracted = assistantTextThinkingAndCalls(message); ++ if (extracted.calls.length === 0) { ++ // Commentary can follow the call items but precede their recorded results. ++ // Keep the batch open and release its text/thinking after the actual results. ++ deferred.push({ ++ role: "assistant", ++ content: extracted.content, ++ ...(extracted.thinking ? { thinking: extracted.thinking } : {}), ++ }); ++ continue; ++ } ++ } + flushPending(); + } + +@@ -443,7 +466,11 @@ function buildNativeMessages( + }; + messages.push(native); + if (wireCalls.length > 0) { +- pending = { calls: wireCalls, byId: new Map(wireCalls.map(call => [call.id, call])) }; ++ pending = { ++ calls: wireCalls, ++ byId: new Map(wireCalls.map(call => [call.id, call])), ++ unresolvedCount: wireCalls.length, ++ }; + } + break; + } +diff --git a/structure/providers/chat-compat.md b/structure/providers/chat-compat.md +index a469a3c8ef..740bb7a115 100644 +--- a/structure/providers/chat-compat.md ++++ b/structure/providers/chat-compat.md +@@ -122,13 +122,13 @@ and synthesizing explicit "no tool result was recorded" answers only when no rea + (Kimi/Moonshot 400 `ocx-mrqaiw05-269`; unit `devlog/_fin/260718_dangling_toolcall_hardening`). + + The native Ollama wire carries the same contract. `src/adapters/ollama-native.ts` +-`buildNativeMessages` defers `user`/`developer` messages that arrive while a batch is open and +-releases them after the tool messages, and answers a call with no result anywhere in the replayed +-history with the same `[ocx] no tool result was recorded for ""` marker. The shape it +-absorbs is ordinary Codex history, not a malformed one: Codex records mid-turn items (a +-`PostToolUse` hook verdict, a context notice) between an assistant `tool_calls` message and that +-call's own result. The strict pair checks (orphan result, duplicate result, result naming another +-tool) still throw on both wires (#4842). ++`buildNativeMessages` defers `user`/`developer` messages while a batch is open; assistant text/thinking ++with no new tool calls is also deferred while results remain outstanding. Deferred messages retain ++arrival order after recorded results. An unresolved counter avoids rescanning calls per message. ++A new tool-call batch settles its predecessor; completed batches keep subsequent messages in place. ++Codex can record hook notices or commentary between calls and results. Missing results keep the ++`[ocx] no tool result was recorded for ""` marker; orphan IDs, duplicates, and mismatched names ++still throw (#4842). `tests/providers/ollama/ollama-native.test.ts` covers replay and compaction. + + Forward-mode OpenAI passthrough also repairs replayed `call_id` values longer than the Responses + API's 64-character limit. Sidechat/fork replay can namespace routed-provider ids beyond that limit, +diff --git a/tests/providers/ollama/ollama-native.test.ts b/tests/providers/ollama/ollama-native.test.ts +index 44a09e8a8c..c17e5c1967 100644 +--- a/tests/providers/ollama/ollama-native.test.ts ++++ b/tests/providers/ollama/ollama-native.test.ts +@@ -9,12 +9,14 @@ import { getProviderRegistryEntry } from "../../../src/providers/registry"; + import { withStubbedProviderFetch } from "../../helpers/catalog-provider-fetch"; + import type { OcxParsedRequest, OcxProviderConfig } from "../../../src/types"; + ++/** Discover routed test models with provider fetches stubbed to avoid live requests. */ + const gatherRoutedModels: typeof gatherRoutedModelsDirect = (config, options) => + gatherRoutedModelsDirect(withStubbedProviderFetch(config), options); + + /** The four ids this transport is maintained against. */ + const TARGETS = ["glm-5.3-flash", "deepseek-v4-flash:0731", "glm-5.2", "kimi-k3"] as const; + ++/** Configure a native Ollama test provider with inert credentials and caller overrides. */ + function ollamaProvider(overrides: Partial = {}): OcxProviderConfig { + return { + adapter: "ollama-native", +@@ -28,6 +30,7 @@ function ollamaProvider(overrides: Partial = {}): OcxProvider + } as OcxProviderConfig; + } + ++/** Build a minimal streamed replay request from synthetic messages and caller options. */ + function parsedWith( + messages: unknown[], + options: Record = {}, +@@ -285,6 +288,120 @@ describe("ollama-native — request shape", () => { + expect(messages[3].content).toBe("[hook] design findings requiring review"); + }); + ++ test("assistant commentary before parallel results keeps the original batch open", async () => { ++ const adapter = createOllamaNativeAdapter(ollamaProvider()); ++ const { body } = await adapter.buildRequest(parsedWith([ ++ { ++ role: "assistant", ++ content: [ ++ { type: "toolCall", id: "call_commentary_first", name: "exec", arguments: { input: "text('first')" } }, ++ { type: "toolCall", id: "call_commentary_second", name: "exec", arguments: { input: "text('second')" } }, ++ ], ++ timestamp: 1, ++ }, ++ { role: "assistant", content: [{ type: "text", text: "checking both results" }], timestamp: 2 }, ++ { role: "toolResult", toolCallId: "call_commentary_second", toolName: "exec", content: "second result", isError: false, timestamp: 3 }, ++ { role: "toolResult", toolCallId: "call_commentary_first", toolName: "exec", content: "first result", isError: false, timestamp: 4 }, ++ ])); ++ const messages = JSON.parse(String(body)).messages; ++ expect(messages.map((message: { role: string }) => message.role)) ++ .toEqual(["assistant", "tool", "tool", "assistant"]); ++ expect(messages[1]).toMatchObject({ tool_call_id: "call_commentary_first", content: "first result" }); ++ expect(messages[2]).toMatchObject({ tool_call_id: "call_commentary_second", content: "second result" }); ++ expect(messages[3].content).toBe("checking both results"); ++ }); ++ ++ test("assistant commentary after one parallel result preserves the remaining genuine result", async () => { ++ const adapter = createOllamaNativeAdapter(ollamaProvider()); ++ const { body } = await adapter.buildRequest(parsedWith([ ++ { ++ role: "assistant", ++ content: [ ++ { type: "toolCall", id: "call_partial_first", name: "exec", arguments: {} }, ++ { type: "toolCall", id: "call_partial_second", name: "exec", arguments: {} }, ++ ], ++ timestamp: 1, ++ }, ++ { role: "toolResult", toolCallId: "call_partial_first", toolName: "exec", content: "first done", isError: false, timestamp: 2 }, ++ { role: "assistant", content: [{ type: "text", text: "waiting for the second result" }], timestamp: 3 }, ++ { role: "toolResult", toolCallId: "call_partial_second", toolName: "exec", content: "second done", isError: false, timestamp: 4 }, ++ ])); ++ const messages = JSON.parse(String(body)).messages; ++ expect(messages.map((message: { role: string }) => message.role)) ++ .toEqual(["assistant", "tool", "tool", "assistant"]); ++ expect(messages[1].content).toBe("first done"); ++ expect(messages[2].content).toBe("second done"); ++ expect(messages[3].content).toBe("waiting for the second result"); ++ }); ++ ++ test("deferred assistant text and thinking retain their order without mutating parsed history", async () => { ++ const parsed = parsedWith([ ++ { role: "assistant", content: [{ type: "toolCall", id: "call_thinking", name: "exec", arguments: {} }], timestamp: 1 }, ++ { role: "developer", content: "context notice", timestamp: 2 }, ++ { role: "assistant", content: [{ type: "text", text: "first comment" }, { type: "thinking", thinking: "synthetic reasoning" }], timestamp: 3 }, ++ { role: "assistant", content: [{ type: "text", text: "second comment" }], timestamp: 4 }, ++ { role: "toolResult", toolCallId: "call_thinking", toolName: "exec", content: "recorded result", isError: false, timestamp: 5 }, ++ ]); ++ const original = JSON.stringify(parsed); ++ const { body } = await createOllamaNativeAdapter(ollamaProvider()).buildRequest(parsed); ++ const messages = JSON.parse(String(body)).messages; ++ expect(messages.map((message: { role: string }) => message.role)) ++ .toEqual(["assistant", "tool", "system", "assistant", "assistant"]); ++ expect(messages[1].content).toBe("recorded result"); ++ expect(messages[2].content).toBe("context notice"); ++ expect(messages[3]).toMatchObject({ content: "first comment", thinking: "synthetic reasoning" }); ++ expect(messages[4].content).toBe("second comment"); ++ expect(JSON.stringify(parsed)).toBe(original); ++ }); ++ ++ test("assistant commentary after a complete batch keeps its existing wire position", async () => { ++ const { body } = await createOllamaNativeAdapter(ollamaProvider()).buildRequest(parsedWith([ ++ { role: "assistant", content: [{ type: "toolCall", id: "call_complete", name: "exec", arguments: {} }], timestamp: 1 }, ++ { role: "toolResult", toolCallId: "call_complete", toolName: "exec", content: "done", isError: false, timestamp: 2 }, ++ { role: "assistant", content: [{ type: "text", text: "completed comment" }], timestamp: 3 }, ++ { role: "user", content: "next turn", timestamp: 4 }, ++ ])); ++ const messages = JSON.parse(String(body)).messages; ++ expect(messages.map((message: { role: string }) => message.role)) ++ .toEqual(["assistant", "tool", "assistant", "user"]); ++ expect(messages[1].content).toBe("done"); ++ expect(messages[2].content).toBe("completed comment"); ++ expect(messages[3].content).toBe("next turn"); ++ }); ++ ++ test("a new tool-call batch still settles an unresolved batch after assistant commentary", async () => { ++ const { body } = await createOllamaNativeAdapter(ollamaProvider()).buildRequest(parsedWith([ ++ { role: "assistant", content: [{ type: "toolCall", id: "call_unfinished", name: "exec", arguments: {} }], timestamp: 1 }, ++ { role: "assistant", content: [{ type: "text", text: "before the next batch" }], timestamp: 2 }, ++ { role: "assistant", content: [{ type: "toolCall", id: "call_next", name: "exec", arguments: {} }], timestamp: 3 }, ++ { role: "toolResult", toolCallId: "call_next", toolName: "exec", content: "next result", isError: false, timestamp: 4 }, ++ ])); ++ const messages = JSON.parse(String(body)).messages; ++ expect(messages.map((message: { role: string }) => message.role)) ++ .toEqual(["assistant", "tool", "assistant", "assistant", "tool"]); ++ expect(messages[1].tool_call_id).toBe("call_unfinished"); ++ expect(messages[1].content).toContain("execution status unknown"); ++ expect(messages[2].content).toBe("before the next batch"); ++ expect(messages[4]).toMatchObject({ tool_call_id: "call_next", content: "next result" }); ++ }); ++ ++ test("routed compaction preserves a result recorded after assistant commentary", async () => { ++ const parsed = parsedWith([ ++ { role: "assistant", content: [{ type: "toolCall", id: "call_compaction", name: "exec", arguments: {} }], timestamp: 1 }, ++ { role: "assistant", content: [{ type: "text", text: "commentary before compaction" }], timestamp: 2 }, ++ { role: "toolResult", toolCallId: "call_compaction", toolName: "exec", content: "genuine result", isError: false, timestamp: 3 }, ++ { role: "user", content: "summarize this history", timestamp: 4 }, ++ ], { toolChoice: "none" }); ++ parsed._compactionRequest = true; ++ const { body } = await createOllamaNativeAdapter(ollamaProvider()).buildRequest(parsed); ++ const request = JSON.parse(String(body)); ++ expect(request.messages.map((message: { role: string }) => message.role)) ++ .toEqual(["assistant", "tool", "assistant", "user"]); ++ expect(request.messages[1]).toMatchObject({ tool_call_id: "call_compaction", content: "genuine result" }); ++ expect(request.messages[2].content).toBe("commentary before compaction"); ++ expect(request.tools).toBeUndefined(); ++ }); ++ + test("a deferred user message keeps its text and images after the tool result", async () => { + const adapter = createOllamaNativeAdapter(ollamaProvider()); + const png = "data:image/png;base64,iVBORw0KGgo="; + +``` + +## Exact additional regression block + +Append this block to the existing uncapped native test file; no shared registry mutation is required. It reuses parsedWith/ollamaProvider and tests the public adapter boundary. + +```typescript + +describe("ollama-native commentary validation and replay ownership", () => { + const call = (id = "call_guard") => ({ + role: "assistant", content: [{ type: "toolCall", id, name: "exec", namespace: "ops", arguments: { cmd: "pwd" } }], timestamp: 1, + }); + const commentary = { role: "assistant", content: [{ type: "text", text: "Working." }], timestamp: 2 }; + const result = (id = "call_guard") => ({ + role: "toolResult", toolCallId: id, toolName: "exec", toolNamespace: "ops", content: "done", isError: false, timestamp: 3, + }); + + for (const [label, invalid, error] of [ + ["unknown id", { ...result(), toolCallId: "call_other" }, /has no originating call/], + ["wrong name", { ...result(), toolName: "other" }, /names the wrong originating tool/], + ["wrong namespace", { ...result(), toolNamespace: "other" }, /names the wrong originating tool/], + ] as const) { + test(`commentary does not bypass result validation: ${label}`, () => { + const adapter = createOllamaNativeAdapter(ollamaProvider()); + expect(() => adapter.buildRequest(parsedWith([call(), commentary, invalid]))).toThrow(error); + }); + } + + test("duplicate results remain rejected while commentary holds an unresolved batch", () => { + const first = call("first"); + const second = call("second"); + const batch = { ...first, content: [...first.content, ...second.content] }; + const adapter = createOllamaNativeAdapter(ollamaProvider()); + expect(() => adapter.buildRequest(parsedWith([batch, result("first"), commentary, result("first")]))).toThrow(/duplicate tool result/); + }); + + test("a result cannot cross a subsequent tool-call batch", () => { + const adapter = createOllamaNativeAdapter(ollamaProvider()); + expect(() => adapter.buildRequest(parsedWith([call("old"), commentary, call("new"), result("old")]))).toThrow(/has no originating call/); + }); + + for (const id of ["", "call_guard"]) { + test(`new batches still reject ${id ? "reused" : "empty"} call IDs after commentary`, () => { + const adapter = createOllamaNativeAdapter(ollamaProvider()); + expect(() => adapter.buildRequest(parsedWith([call(), commentary, call(id)]))).toThrow(/id is missing or duplicated/); + }); + } + + test("EOF settles a missing result once before deferred commentary", () => { + const { body } = createOllamaNativeAdapter(ollamaProvider()).buildRequest(parsedWith([call(), commentary])); + const messages = JSON.parse(body).messages; + expect(messages.map((message: { role: string }) => message.role)).toEqual(["assistant", "tool", "assistant"]); + expect(messages[1].tool_call_id).toBe("call_guard"); + expect(messages[1].content).toContain("execution status unknown"); + expect(messages[2].content).toBe("Working."); + }); + + test("completed batches release commentary before the next conversation turn", () => { + const { body } = createOllamaNativeAdapter(ollamaProvider()).buildRequest(parsedWith([ + call(), result(), commentary, { role: "user", content: "next" }, call("next"), result("next"), + ])); + const messages = JSON.parse(body).messages; + expect(messages.map((message: { role: string }) => message.role)).toEqual(["assistant", "tool", "assistant", "user", "assistant", "tool"]); + expect(messages[2].content).toBe("Working."); + expect(messages[3].content).toBe("next"); + expect(messages[5].tool_call_id).toBe("next"); + }); + + test("deep-frozen history keeps call order, deferred arrival order and original arguments", () => { + const first = call("first"); + const second = call("second"); + const batch = { ...first, content: [...first.content, ...second.content] }; + const parsed = parsedWith([ + batch, + { role: "developer", content: "hook" }, + result("second"), + { role: "assistant", content: [{ type: "thinking", thinking: "Checking." }, { type: "text", text: "Working." }] }, + { role: "user", content: "notice" }, + result("first"), + ]); + function freeze(value: unknown): void { + if (!value || typeof value !== "object" || Object.isFrozen(value)) return; + for (const child of Object.values(value)) freeze(child); + Object.freeze(value); + } + const before = JSON.stringify(parsed); + freeze(parsed); + const { body } = createOllamaNativeAdapter(ollamaProvider()).buildRequest(parsed); + expect(JSON.stringify(parsed)).toBe(before); + const messages = JSON.parse(body).messages; + expect(messages.map((message: { role: string }) => message.role)).toEqual(["assistant", "tool", "tool", "system", "assistant", "user"]); + expect(messages.slice(1, 3).map((message: { tool_call_id: string }) => message.tool_call_id)).toEqual(["first", "second"]); + expect(messages.slice(3).map((message: { content: string }) => message.content)).toEqual(["hook", "Working.", "notice"]); + expect(messages[4].thinking).toBe("Checking."); + expect(messages[0].tool_calls[0].function.arguments).toEqual({ cmd: "pwd" }); + }); +}); +``` diff --git a/devlog/_plan/261003_release_lane_a/000_plan.md b/devlog/_plan/261003_release_lane_a/000_plan.md new file mode 100644 index 00000000000..7e51c81ee4d --- /dev/null +++ b/devlog/_plan/261003_release_lane_a/000_plan.md @@ -0,0 +1,46 @@ +# Preserve large native turns and terminal input-limit errors + +Lane A will carry the native upload representation fix from PR #6508 and repair the loss of terminal context errors when Responses output becomes Claude Messages. The transport fix prevents a known large-string upload failure mode; the error fix lets a client distinguish input rejection from a retryable transport failure. Neither increases upstream context capacity or guarantees recovery of the issue reporter's private session. + +Reader: the release coordinator and reviewers decide whether this lane is safe to integrate; they already know the release scope. + +## Loop contract + +- Archetype/trigger: satisfy-spec HOTL release stabilization, issue #6504 and PR #6508. +- Goal: byte-correct single native HTTP sends and classified non-retryable Messages context errors, published on this lane's attributed PR with exact-head checks. +- Non-goals: credential/account changes, model capacity changes, silent payload pruning, retries, merging, releasing, installed app changes, source-PR closure. Lane B owns core-auth/core-options/core-combo; coordinate through main for any needed overlap. +- Verifiers: focused transport and Messages regression files plus typecheck/privacy/structure/docs build; each conditional scenario below asserts observable output and send counts. Tests are named directly and observe changed source through imports. Full local suite exception is concurrent worktree contention; hosted applicable PR CI remains mandatory. Initial baseline failure was missing zod/v4; locked dependency installation subsequently succeeded and transport baseline passed 27/27. +- Stop: all scoped changes reviewed/published with required applicable exact-head CI successful and report complete. This lane does not merge. +- Artifacts: this numbered unit, ignored `.tmp/release-stabilization/` receipts and security notes, bound `.codexclaw` goalplan/FSM. +- Outcomes: DONE requires evidence; NOOP only if current dev contains the behavior; blocked/unsafe/needs-human means a named unresolved condition without weakened criteria. No user token/time budget; bounded commands, existing credentials only, no new paid resources. +- Escalation: conflicting ownership, upstream capacity ambiguity, absent CI capability or unresolved review; continue independent work. User scope remains authoritative. +- Coordinator gate: ROADMAP LOCKED received; coordinator docs-only commit 2152fdfeb8 with architect/reviewer PASS. Lane's own roadmap P/A remains required. + +## Dependency map and file map + +1. Roadmap: this master, 001 evidence, and 010/020/030 executable plans. Independent architect reflection and independent A audit before implementation. +2. Transport foundation: 010 carries source commit into our branch with its history/credit, then strengthens threshold/destination/cancellation tests; source and docs paths are listed in its exact diff. +3. Messages projection: 020 changes only Claude/encoder/ingress error projection, adjacent tests and owning docs. Tests reuse the now verified transport substrate; no credential code edits. +4. Final review/publication: 030 independently challenges both paths in parallel, resolves findings, verifies docs/gates, publishes ordinary dev PR and records successful applicable exact-head CI. + +The repository is Bun-native TypeScript. Existing owners: src/server/responses transport; src/claude translation; src/protocols/encoders direct Messages encoding; tests/{responses,claude-integration}; structure current contracts; docs-site user workflows. Reuse these, with no new architecture or dependency. + +## Architect consultation and main decisions + +V1 subagent transport is available (send_input/close_agent); inherited model/effort, independent prompt context. Architect handle: 01a1020f-456a-7bf1-bde1-6e2edd721258. Proposal D1–D5 received against b82b39018b48ad489110b4165ee2cd9ba30433d5. + +- D1 accepted, narrowed to the existing exact `context_length_exceeded` identity rather than new message heuristics or aliases; preserve that code through both encoders. +- D2 accepted: both non-stream collectors, defensive failed JSON and non-2xx reshaping must preserve the recognized context semantics. Unknown failures retain prior 502/529 behavior. +- D3 accepted: existing Responses 413 normalization owns safe generic copy. No new retry, account switch, tool stripping or compaction execution. +- D4 accepted: final destination, UTF-8 threshold, metadata and single-send invariants. Source carry is independent from error translation. +- D5 accepted with reachable-path refinement: native Responses passthrough does not enter direct encoders, so real native ingress tests cover legacy path and direct AdapterEvent tests separately activate direct encoding. Toggle parity alone is insufficient proof of direct execution. + +Same-handle reflection ALIGNED on this concrete revision; three evidence clarifications incorporated (001). Independent A review GO-WITH-FIXES (blockers=0); all nonblocking clarifications incorporated. Roadmap approved for implementation after document receipt. + +## Source-of-truth sync and scope + +Update structure/transports/responses.md and byte-accounting.md for physical upload representation, structure/data-planes/inbound-compat.md for error projection, and docs-site server/Claude guide for behavior and recovery limits. Review affected map owners; do not copy unchanged contracts. Source fixes are not proof of the reporter's actual Claude retry/compaction behavior; synthetic wire proof and any actual client observation must be labeled separately. + +## Roadmap cycle conclusion + +Docs-only roadmap approved, source comparison and baseline evidence recorded. Next cycle carries PR6508 and verifies final-send invariants, followed by Messages projection. No runtime fixes are claimed by this cycle. Source-author live success and private-session issue causality remain unverified here. diff --git a/devlog/_plan/261003_release_lane_a/001_evidence.md b/devlog/_plan/261003_release_lane_a/001_evidence.md new file mode 100644 index 00000000000..377cc58d622 --- /dev/null +++ b/devlog/_plan/261003_release_lane_a/001_evidence.md @@ -0,0 +1,36 @@ +# Source inspection and verification evidence + +- Worktree initial HEAD b82b39018b48ad489110b4165ee2cd9ba30433d5; fetched dev cb2d1736a858a69b3d95944d531f48b93c2f56a5 differs only in CLI/help files. Revalidate before implementation and publication. +- Source PR #6508 open at dd4fc9a8f732699d88f46058c3298073d9aa2317, author Maxy Milan Sorée ; five changed paths, no returned reviews/comments, stacks API returned []. Source body reports a synthetic native macOS/Bun repro; it is not our runtime proof. +- Issue #6504 lacks complete client wire capture; the reported recovery is anecdotal. Current catalog/launcher docs do not establish a guaranteed 1M native upstream window. +- src/claude/outbound.ts:249 derives failed status, but :726 drops code at fail. src/protocols/encoders/messages.ts:390 does likewise. src/server/claude-messages.ts:1398 and direct encoder :449 collapse collected errors to 502. The JSON/non-2xx branches also discard context identity. +- src/server/responses/context-overflow.ts:19 produces classified context terminal events for provider HTTP 413 with proxy-owned copy. Preserve this owner. +- src/server/inference/client-encoder-delivery.ts:46 excludes Responses-wire routes from direct encoding; direct encoder tests must exercise AdapterEvents. +- `bun install --frozen-lockfile --ignore-scripts`: exit0, existing lockfile unchanged. Install log in ignored scratch. +- Initial transport baseline: exit1, missing zod/v4, no behavior executed. After install same three explicit test files: exit0, 27 pass, 0 fail, 64 assertions; .tmp/release-stabilization/transport-baseline-installed.log. +- New helper owner search: `context_length_exceeded`, `anthropicFailedStatus`, `anthropicErrorBody`, `collectAnthropicMessageResponse`; reuse outbound error classification and exact code comparisons, not a parallel classifier. +- Private conversation capture and real paid upstream probes have not run. Synthetic fixtures are sufficient for proxy wire regression proof; native client compaction is a separate evidence limit. + +- Messages baseline: bun test tests/claude-integration/claude-outbound.test.ts tests/responses/protocol-direct-encoders-messages.test.ts tests/responses/responses-context-overflow.test.ts exited0: 143 pass, 0 fail, 597 assertions (messages-baseline.log). + +- Architect same-handle reflection: ALIGNED; D1-D5 map to plan. Incorporated baseline freshness, executor-vs-network distinction, Retry-After activation and failed-JSON reachability clarifications. No material design gaps. + +- Independent A reviewer 01a10218-d4e7-7f63-9f01-8582de5584be: GO-WITH-FIXES (blockers=0). Main accepted all four clarifications: failed-JSON reachability, prior exact assertion update, non2xx400 versus precedence negatives, baseline freshness/exact future command. No design/ownership expansion. + +- Docs-only B finalized the approved roadmap and checked that no runtime paths changed. Synthetic isolated Claude Code2.1.288 local HTTP400 probe exited1 in0.38s displaying API Error400; two local Messages requests were observed, so this proves surfacing/prompt termination only, not zero retry or automatic compaction. No private conversation or real provider was used; ignored probe artifact retained. + +- Transport P revalidation: previous D direction was carry6508 then Messages. Rebased docs commit onto cb2d1736a8; transport paths are byte-identical to the architect/auditor reviewed source. 010 remains the executable plan with unchanged D4 decisions; existing proposal/reflection and whole-roadmap audit remain applicable. + +- Transport B: cherry-picked6508 with -x (0076dc5b0b), original author retained; added UTF-8 exact threshold, destination-away and AbortError executor tests. Focused initial green exit0. Removing byte conversion made the boundary tests fail (transport-mutation-red.log); source restored before broader affected transport checks. No retries or credential policy altered. + +- Transport C/D: 199 passed,0 failed,1 older-runtime skip; mutation6fail. Typecheck/privacy/structure exit0; docs561 pages/77932links pass. Pinned Bun postinstall was completed after ignore-scripts prevented bun-run commands; no lockfile change. Independent code/security reviewer01a10221-1acd-7f90-92c9-ae96f95229f1 returned PASS, no blockers, four extra synthetic boundary probes passed. Next: publish transport and continue Messages. + +- Messages P revalidation: transport D concluded reviewed upload bytes are ready; continue020 per roadmap. All three Messages runtime files remain identical to original architect/reflection/audit source. Same D1-D3/D5 decisions apply with accepted reachability clarifications; no credentials/compaction-policy edits. Transport published as PR6513, and Messages is a separate child branch pending parent integration. + +- Messages B: new real HTTP Messages regressions first failed8 cases (502 collection/JSON, missing stream code, retry hint leak), then passed. Restoring all three pre-fix source files with current regressions caused13 failures; restored fixes afterward. A canonical native final-send fixture also asserts actual Messages SSE/JSON and exactly1 upstream send. Explicit Direct stored-main variant encountered the separately owned auth fence (requires caller bearer); used the existing canonical-forward fixture pattern without changing credential owners. +- Messages affected command from020 passed211 tests/0 failures across5 files. Defensive upstream JSON remained JSON into the ingress: stream true also returns HTTP400 as asserted. Existing endpoint negatives preserve unrelated failures. +- Composed real-client probe: isolated Claude Code2.1.288 -> proxy /v1/messages -> canonical native Responses executor redirected to synthetic upstream response.failed/context_length_exceeded. Exited1 in0.765s,3 bounded upstream requests, displays API Error400 and synthetic input-limit message, no timeout. Client uses known Claude model name mapped in isolated config to native GPT destination. It proves composed surfacing/termination, not zero retries, real account behavior or automatic compaction. First unrecognized fixture alias was rejected by client before upstream (0hits) and is not counted as passing evidence. Scratch script/log retained; no private conversation or external provider used. + +- Test placement amendment: registering a new Messages test hit NEW_OVERSIZED at2000lines in test-layout-expected.json. Move the new cases into the existing claude-outbound.test.ts under a nested fixture scope (remains below2000), remove only our new registry row/file; preserve ratchet, no cap increase or formatting workaround. Runtime/interface/flow decisions unchanged. + +- Messages C/D: after relocating fixtures,238 tests passed/0fail across7files including layout/ratchet; typecheck/privacy/structure exit0,docs561pages/77932links pass. Independent implementation/security reviewer01a10230-ce6c-75c3-b677-9edafdd2b12a PASS, blockers0; additional partial-output/cancellation/exact-code-negative probes passed. Next publish Messages child and complete exact-head hosted evidence; no claims of native account/automatic-compaction success. diff --git a/devlog/_plan/261003_release_lane_a/010_native_upload.md b/devlog/_plan/261003_release_lane_a/010_native_upload.md new file mode 100644 index 00000000000..2a44f3ba9b5 --- /dev/null +++ b/devlog/_plan/261003_release_lane_a/010_native_upload.md @@ -0,0 +1,202 @@ +# Encode large native HTTP uploads at the physical send + +Depends on the docs-only roadmap audit and coordinator ROADMAP LOCKED. This document proposes changes; none have been applied. + +Carry source PR #6508 commit `dd4fc9a8f732699d88f46058c3298073d9aa2317` by Maxy Milan Sorée, preserving the original commit with `git cherry-pick -x` after rebasing this task branch onto current dev. Add `Co-authored-by: Maxy Milan Sorée ` to the eventual PR description. Do not alter or close the source PR. + +The transport code currently forwards a serialized string unchanged at `src/server/responses/fetch-helpers.ts:195`. The carry encodes only native Responses and compact strings at least 1 MiB in UTF-8, after final URL reconstruction and plugin rewrite. It leaves WebSocket selection, non-native destinations, headers, abort signal and replay policy unchanged. The extra allocation is bounded by existing request admission, not a new retained context store. Keep the existing transport leaf; no new module or dependency is justified for this small boundary change. + +## Exact source carry + +All five paths below are MODIFY. Apply the source delta, then revalidate against the branch's current source. No deletion or credential-owner change is planned. + +```diff +diff --git a/docs-site/src/content/docs/reference/configuration/server.md b/docs-site/src/content/docs/reference/configuration/server.md +index 5a434cfa4c..25198749be 100644 +--- a/docs-site/src/content/docs/reference/configuration/server.md ++++ b/docs-site/src/content/docs/reference/configuration/server.md +@@ -79,6 +79,11 @@ refusal as rate-limit or quota evidence against the credential it was holding. T + requests such as vision and web search are replayed normally, because repeating them cannot + duplicate a turn. + ++Large native ChatGPT Responses and compact HTTP requests use UTF-8 byte-buffer uploads to avoid ++Bun resetting a large string upload before response headers arrive. This preserves the request ++contents and does not enable automatic retries. A genuine connection reset still follows the ++replay-refusal policy above. ++ + A native Responses provider can opt into replacing that send with + [`retryOnReset`](/reference/configuration/providers/#provider-entries-ocxproviderconfig). The same grant covers the + case where the connection survives the header and the SSE body then dies carrying only control +diff --git a/src/server/responses/fetch-helpers.ts b/src/server/responses/fetch-helpers.ts +index 524a026cf6..610b7c2cac 100644 +--- a/src/server/responses/fetch-helpers.ts ++++ b/src/server/responses/fetch-helpers.ts +@@ -192,8 +192,17 @@ export function sendWithConnectionPolicy( + const egressInit = redirectedToLoopback + ? { proxy: false as const } + : decide ? providerEgressSendInit(egress, physicalFetch, input) : {}; ++ // Bun can reset large native Codex string uploads before receiving headers while ++ // the identical UTF-8 buffer succeeds. Convert only at the final HTTP send so ++ // WebSocket selection still sees the serialized string and no retry is added. ++ const target = input instanceof Request ? input.url : String(input); ++ const body = init?.body; ++ const largeCodexBody = typeof body === "string" ++ && /^https:\/\/chatgpt\.com\/backend-api\/codex\/responses(?:\/compact)?$/.test(target) ++ && Buffer.byteLength(body, "utf8") >= 1024 * 1024; + return physicalFetch(input, { + ...init, ++ ...(largeCodexBody ? { body: Buffer.from(body, "utf8") } : {}), + headers, + redirect: "manual", + ...(fresh ? { keepalive: false } : {}), +diff --git a/structure/transports/byte-accounting.md b/structure/transports/byte-accounting.md +index 2e1c59f23b..9714dc7fef 100644 +--- a/structure/transports/byte-accounting.md ++++ b/structure/transports/byte-accounting.md +@@ -27,6 +27,11 @@ hard byte cap. Per-body limits, parsing, compression, and reader error envelopes + `tests/usage/request-decompress.test.ts` covers exact accounting across codecs and Unicode/numeric + normalization, UTF-8 counting without encoded copies, and release after malformed or optional empty input. + ++The final native ChatGPT Responses HTTP send in `src/server/responses/fetch-helpers.ts` encodes JSON ++strings of at least 1 MiB as a UTF-8 buffer for Bun upload compatibility. This is a transport copy, ++not a counting allocation or retained continuation. Existing body admission limits still apply; ++the serialization observation keeps its existing lifetime, and nested dispatch reuses the buffer. ++ + ## Raised HTTP body admission + + `src/server/inbound-body-admission.ts` reserves the full resolved `maxInboundBodyBytes` allowance +diff --git a/structure/transports/responses.md b/structure/transports/responses.md +index 9dcb4a939a..0eeb406505 100644 +--- a/structure/transports/responses.md ++++ b/structure/transports/responses.md +@@ -49,7 +49,7 @@ keep-alive reuse with `Connection: close` and `keepalive: false`; exact hosts an + match case-insensitively. `sendWithConnectionPolicy` applies the policy around the fetch that + performs the physical send, after a dispatch override has selected or rebuilt the destination, so + matching follows the URL sent on the wire rather than the URL supplied before credential +-revalidation. ++revalidation. At this final HTTP boundary, native ChatGPT Responses and compact JSON strings of at least 1 MiB (UTF-8) become byte buffers to avoid Bun's large-string upload resets. Content, headers, abort signals and retry policy are preserved; WebSocket selection still receives the original string. Other destinations, small strings and existing byte/stream bodies retain their representation. + + The wrapped executor alone is not that boundary. An override that revalidates credentials re-reads + `route.provider.fetch` at send time, because reselection can install a different provider transport +diff --git a/tests/responses/responses-fetch-helpers-boundary.test.ts b/tests/responses/responses-fetch-helpers-boundary.test.ts +index 1f59e4ef4b..3a1033a66f 100644 +--- a/tests/responses/responses-fetch-helpers-boundary.test.ts ++++ b/tests/responses/responses-fetch-helpers-boundary.test.ts +@@ -3,12 +3,105 @@ import { readFileSync } from "node:fs"; + import { dirname, resolve } from "node:path"; + import { fileURLToPath } from "node:url"; + import { createScanner, LanguageVariant, SyntaxKind } from "typescript/unstable/ast"; +-import { fetchWithHeaderTimeout, storedPoolReplayDispatchNotifier } from "../../src/server/responses/fetch-helpers"; ++import { fetchWithHeaderTimeout, providerFetch, sendWithConnectionPolicy, storedPoolReplayDispatchNotifier } from "../../src/server/responses/fetch-helpers"; + import { repoRoot as resolveRepoRoot } from "../helpers/repo-root"; + + const repoRoot = resolveRepoRoot(); + const helperPath = resolve(repoRoot, "src/server/responses/fetch-helpers.ts"); + ++describe("native Codex HTTP upload representation", () => { ++ const endpoint = "https://chatgpt.com/backend-api/codex/responses"; ++ ++ function captureFetch() { ++ const calls: { input: Parameters[0]; init?: RequestInit }[] = []; ++ const execute = (async (input, init) => { ++ calls.push({ input, init }); ++ return new Response("ok"); ++ }) as typeof fetch; ++ return { calls, execute }; ++ } ++ ++ test("sends large Unicode JSON as identical UTF-8 bytes without changing request metadata", async () => { ++ // Fewer than 1 MiB of JS code units, but over 1 MiB on the wire. ++ const body = JSON.stringify({ input: "é🦊".repeat(200_000) }); ++ const signal = new AbortController().signal; ++ const headers = { "content-type": "application/json", "x-request-test": "preserved" }; ++ const init: RequestInit = { method: "POST", body, headers, signal }; ++ const capture = captureFetch(); ++ ++ await sendWithConnectionPolicy(capture.execute, endpoint, init); ++ ++ expect(capture.calls).toHaveLength(1); ++ const sent = capture.calls[0]!; ++ expect(sent.input).toBe(endpoint); ++ expect(sent.init?.body instanceof Uint8Array).toBe(true); ++ expect(Buffer.from(sent.init!.body as Uint8Array).equals(Buffer.from(body, "utf8"))).toBe(true); ++ expect(sent.init?.method).toBe("POST"); ++ expect(sent.init?.signal).toBe(signal); ++ expect(Object.fromEntries(new Headers(sent.init?.headers))).toEqual(headers); ++ expect(sent.init?.redirect).toBe("manual"); ++ expect(init.body).toBe(body); ++ }); ++ ++ test("covers compact and rebuilt destinations at the final HTTP dispatch", async () => { ++ const body = JSON.stringify({ input: "x".repeat(1024 * 1024) }); ++ const capture = captureFetch(); ++ const send = providerFetch({ adapter: "openai-responses", baseUrl: "https://gateway.example/v1", fetch: capture.execute }, undefined, { ++ httpOnly: true, ++ dispatchOverride: (_input, init, execute) => execute(new URL(`${endpoint}/compact`), init), ++ }); ++ ++ await send("https://gateway.example/v1/responses", { method: "POST", body }); ++ ++ expect(capture.calls).toHaveLength(1); ++ expect(String(capture.calls[0]!.input)).toBe(`${endpoint}/compact`); ++ const sent = capture.calls[0]!.init?.body; ++ expect(sent instanceof Uint8Array).toBe(true); ++ expect(Buffer.from(sent as Uint8Array).equals(Buffer.from(body, "utf8"))).toBe(true); ++ }); ++ ++ test("preserves small strings and already encoded bodies by identity", async () => { ++ const capture = captureFetch(); ++ const bodies: BodyInit[] = ["{}", Buffer.alloc(1024 * 1024, 120), new Blob(["unchanged"]), new ReadableStream()]; ++ for (const body of bodies) { ++ await sendWithConnectionPolicy(capture.execute, new Request(endpoint), { method: "POST", body }); ++ expect(capture.calls.at(-1)!.init?.body).toBe(body); ++ } ++ expect(capture.calls).toHaveLength(bodies.length); ++ }); ++ ++ test("leaves other hosts, schemes, ports and endpoint paths unchanged", async () => { ++ const body = "x".repeat(1024 * 1024); ++ const capture = captureFetch(); ++ for (const target of ["https://api.openai.com/v1/responses", "https://gateway.example/v1/responses", ++ "http://chatgpt.com/backend-api/codex/responses", "https://chatgpt.com:8443/backend-api/codex/responses", ++ "https://chatgpt.com/backend-api/codex/models", "https://chatgpt.com/backend-api/codex/responses/other"]) { ++ await sendWithConnectionPolicy(capture.execute, target, { method: "POST", body }); ++ expect(capture.calls.at(-1)!.init?.body).toBe(body); ++ } ++ }); ++ ++ test("nested dispatch preserves the encoded buffer and propagates failure without retry", async () => { ++ const body = "x".repeat(1024 * 1024); ++ const failure = Object.assign(new TypeError("test connection reset"), { code: "ECONNRESET" }); ++ let calls = 0; ++ let encoded: BodyInit | null | undefined; ++ const physical = (async (_input, init) => { ++ calls += 1; ++ expect(init?.body).toBe(encoded); ++ throw failure; ++ }) as typeof fetch; ++ const nested = ((input, init) => { ++ encoded = init?.body; ++ expect(encoded instanceof Uint8Array).toBe(true); ++ return sendWithConnectionPolicy(physical, input, init); ++ }) as typeof fetch; ++ ++ await expect(sendWithConnectionPolicy(nested, endpoint, { method: "POST", body })).rejects.toBe(failure); ++ expect(calls).toBe(1); ++ }); ++}); ++ + interface RuntimeImportScan { + specifiers: string[]; + nonLiteralDynamicImports: string[]; +``` + +## Additional regression obligations + +MODIFY `tests/responses/responses-fetch-helpers-boundary.test.ts` in its native-upload describe block: parameterize ASCII bodies at 1 MiB minus one, exactly 1 MiB, and 1 MiB plus one; assert string identity below and exact byte identity at/above. Add a rejected AbortError case with an already-aborted signal and verify one executor invocation (not a physical network send), same signal, same rejection, and no retry. Existing fresh-connection and egress cases must remain green. Include a native-to-nonnative rebuilt-destination negative alongside the source nonnative-to-native compact case; final destination owns applicability. + +Activation evidence: a multibyte JSON body below one MiB in JS code units but above one MiB in bytes must arrive at the capture fetch as identical Uint8Array bytes. Nested calls retain the same buffer and call the physical sender once on ECONNRESET. Exact host/path negatives remain strings. No endpoint or native-account behavior is inferred from this test double. + +Verification command: `bun test tests/responses/responses-fetch-helpers-boundary.test.ts tests/responses/fresh-connection-optout.test.ts tests/responses/provider-egress-fetch.test.ts`. Baseline attempt on 2026-10-03 exited 1 before behavioral execution: zod/v4 dependency missing (0 pass, 3 loader errors); see `.tmp/release-stabilization/transport-baseline.log`. The command names all target files directly. After gate release, locked dependencies were installed (ignore-scripts) and the baseline passed 27 tests / 0 failures. Do not rerun unchanged baseline for confidence; source author evidence remains separate. + +Run typecheck, privacy and structure before review readiness. Build docs-site after its locked install because user docs change. Full local suite exception: concurrent release worktrees share resources; focused regression scope is explicit, and applicable hosted PR CI remains mandatory. A synthetic live upload is optional feasibility work only, with no private conversation capture and no new paid resource; source author's macOS/Bun success is historical evidence, not our live proof. diff --git a/devlog/_plan/261003_release_lane_a/020_messages_context.md b/devlog/_plan/261003_release_lane_a/020_messages_context.md new file mode 100644 index 00000000000..112244b819b --- /dev/null +++ b/devlog/_plan/261003_release_lane_a/020_messages_context.md @@ -0,0 +1,64 @@ +# Preserve context rejection through Messages error projection + +Depends on roadmap lock/audit and verified transport foundation. MODIFY existing owners only; no new error codes or type fields, so creation remains existing Responses context classification, serialization is the two Messages encoders, deserialization is collectAnthropicMessage, consumers are the HTTP collectors and Claude client. This is a preservation fix, not a new token limit or recovery mechanism. + +## Exact runtime deltas + +MODIFY src/claude/outbound.ts in `anthropicFailedStatus`: + +```diff + const code = typeof error.code === "string" ? error.code : undefined; ++if (code === "context_length_exceeded") return 400; + return code === "translation_buffer_limit" +``` + +In response.failed's call to `fail(status, message, true)`, add fourth argument `code === "context_length_exceeded" ? code : undefined`. This preserves only the requested recognized code. All unknown code exposure remains unchanged. The existing translator-overflow branch retains its 413 identity. + +MODIFY src/protocols/encoders/messages.ts terminal failed mapping: + +```diff +-fail(anthropicFailedStatus(error, message), message, true); ++fail(anthropicFailedStatus(error, message), message, true, ++ error.code === "context_length_exceeded" ? error.code : undefined); +``` + +In its collected-response status: + +```diff +-status: isError ? 502 : 200, ++status: isError ? (translatedError?.code === "context_length_exceeded" ? 400 : 502) : 200, +``` + +Update the collector doc comment to identify classified context rejection as 400 and unknown error frames as 502. Keep translator overflow 413 handling as-is. + +MODIFY src/server/claude-messages.ts: + +- Non-2xx error branch: initialize `let contextError = false`; add `code?: unknown` to parsed nested error type, and set contextError only for an object whose code is exactly context_length_exceeded. Preserve message extraction. Make `transient` false for contextError, choose outStatus 400 after replay-refusal and native-maintenance precedence, and pass context_length_exceeded as the error code only for this recognized case. Suppress Retry-After for this terminal context response; retain all other refusal/backoff behavior. +- Legacy collected status: use the identical narrow conditional from the direct collector above. +- HTTP 200 JSON status failed: after existing translation-buffer handling and before generic 502 return, add: + +```ts +if (error?.code === "context_length_exceeded") { + return anthropicErrorResponse(400, error.message ?? "upstream context limit exceeded", "invalid_request_error", error.code); +} +``` + +No change to core-auth.ts/core-options.ts/core-combo.ts or Responses retry/compaction policy. Reuse the current source classification, no message regex. Unknown error semantics stay unchanged. + +## Test delta and activation + +MODIFY tests/claude-integration/claude-outbound.test.ts with a nested Messages-ingress describe and locally scoped home/server fixtures. Reuse this existing registered file: a new roster row would push test-layout-expected.json to the prohibited 2,000-line threshold. No ratchet increase or compressed manifest rows. Reuse isolatedCodexHome and per-test OPENCODEX_HOME helpers from adjacent endpoint tests; fixtures contain synthetic input and credentials only. Public handleClaudeMessages with configured openai-responses local upstream activates the actual error translation. Include Retry-After in the non-2xx context fixture to prove suppression; for failed JSON assert upstream content type and record whether earlier normalization consumes it. Use HTTP 400 for the single-send non-2xx fixture; refusal/maintenance precedence stays covered separately by existing endpoint tests. Failed JSON must use a route/config that does not reframe it as SSE; if no public route reaches it, report the coverage limit instead of claiming activation. Parameterize stream true/false and terminal SSE/provider 413/non-2xx context error/failed JSON where constructible. Assert exact invalid_request_error + context_length_exceeded, HTTP 400 for nonstream/JSON, no retry header, one upstream send, one terminal stream error and no message_stop. 413 fixture embeds a private-marker string in upstream body; final error must contain only proxy-safe copy. Add unknown and transient negative cases preserving existing classification. Cancel the response consumer and assert upstream cleanup/no extra dispatch where the harness can observe it. + +MODIFY tests/claude-integration/claude-outbound.test.ts: update the existing classified invalid_request_error exact assertion to include context_length_exceeded; synthetic response.failed with status 413 + context code and status-absent context code both produce invalid_request_error/context_length_exceeded without message_start or successful terminal. Explicit expectations, not parity. + +MODIFY tests/responses/protocol-direct-encoders-messages.test.ts: direct AdapterEvent error with code context_length_exceeded reaches streaming frame and folded HTTP 400; keep explicit oracle assertions. Update the test's legacyFold fixture narrow status rule to mirror the new ingress, without deriving expectations from production helpers. Keep unknown failures and translator limits in existing cases. + +Run explicit test files plus tests/responses/responses-context-overflow.test.ts and existing claude-messages-endpoint.test.ts. Direct Arguments observe each target; baseline three-file command passed 143 tests (001); new-file and endpoint coverage will run after implementation. Exact implementation command: `bun test tests/claude-integration/claude-outbound.test.ts tests/claude-integration/claude-messages-endpoint.test.ts tests/responses/protocol-direct-encoders-messages.test.ts tests/responses/responses-context-overflow.test.ts`. Add source-oracle test layout/file-size checks if a new test file is added. No source cap is to be increased. + +## Exact documentation additions + +MODIFY structure/data-planes/inbound-compat.md: add a short Messages error projection paragraph naming src/claude/outbound.ts, src/protocols/encoders/messages.ts and src/server/claude-messages.ts: classified context_length_exceeded remains invalid_request_error through SSE and HTTP 400 through collection/failed JSON; unknown transient/transport and local translator limits retain their distinct handling. No implicit recovery send. + +MODIFY docs-site/src/content/docs/guides/claude-code.md after Auto context warning: explain that a model's advertised window is not a guarantee that a tool-heavy serialized request fits. A classified input-limit error is terminal; reduce current input or compact earlier. If manual compact also exceeds input limits, preserve original history and compact a fork with fewer enabled tool/MCP schemas if the client supports it. Do not promise that changing client accounting or a [1m] marker raises upstream limits; recommend inspecting actual model metadata rather than hardcoding reporter measurements as supported thresholds. + +Verify relevant translated pages do not contradict changed semantics; do not introduce a new setting or change auto-context defaults. Run docs build and structure check after changes. diff --git a/devlog/_plan/261003_release_lane_a/030_review_publication.md b/devlog/_plan/261003_release_lane_a/030_review_publication.md new file mode 100644 index 00000000000..c86eb3205c9 --- /dev/null +++ b/devlog/_plan/261003_release_lane_a/030_review_publication.md @@ -0,0 +1,16 @@ +# Publish reviewed Lane A evidence for coordinator integration + +Depends on verified transport and Messages changes. No runtime edits are pre-planned here; findings cause a documented amendment and focused repair, not unrelated cleanup. + +1. Run typecheck/privacy/structure and docs-site build, recording full output in ignored scratch. Applicable source-oracle/layout tests run explicitly. Concurrent release worktrees justify the documented full-local-suite exception; do not claim full suite green. +2. Coordinator clarification: the separate final parallel phase is an integrated-candidate gate owned by main, not a lane publication barrier. Publish each coherent ready slice after normal independent review and its focused tests. For lane review, use inherited-model independent-context leaves as useful. One adversarially reviews transport threshold/UTF-8/destination/replay/cancellation; the other reviews Messages terminal status/code/collection/local-limit negatives and security boundaries. Reviewers inspect exact slice diff, report file coverage and blocking findings, and add tests/probes only for unresolved risks. Neither owns goal/FSM, spawns, or mutates branches. Main addresses real findings and re-verifies changes; maintainers' unresolved objections remain blockers. +3. Explicit security review records assets (credentials, request content, replay identity), input/output trust boundaries, attacker-controlled URL/error fields, final destination restriction, no redirect widening, no payload logging and bounded additional allocations. Unpublished security notes remain .tmp/release-stabilization only. +4. Revalidate dev/source PR heads and source disposition, include source-history and Co-authored-by credit, then commit and push only our codex branches. Publish the ready transport slice on codex/release-261003-a, then continue Messages on codex/release-261003-a-messages (same worktree) with an ordinary child PR or dev PR after parent integration. Open the first ordinary PR against dev, filling Summary/Verification/Checklist. No native stack selection, source PR closure, merge or release. +5. Inspect current PR head and applicable required jobs. Missing/skipped/approval-blocked/cancelled results are not passing. Dispatch authorized required PR CI using actual workflow schema; repair observed failures and re-run only invalidated checks. Coordinator owns final lane=all and serial integration. +6. NEW/UPDATE .tmp/release-stabilization/report.md with exact local/PR head, URL, source heads/credit/dispositions, test outputs, review evidence and residual native limitations. Do not expose private data. DONE only after all required applicable exact-head checks actually succeed and blocking reviews resolve. + +Publication changes only Git history and PR metadata. Checklist claims are evidence-based; security review is technical, not a substitute for any maintainer decision. + +## Prepared handoff + +Transport PR6513 has independent code/security PASS and all applicable local checks. Its CodeRabbit threshold wording finding is fixed atdb094c49c7 and the thread resolved; corrected-head hosted CI remains the integration gate. Messages has independent code/security PASS and238 local tests plus typecheck/privacy/structure/docs success. Its child branch includes the parent threshold wording fix without runtime drift. Report and live exact-head CI/review snapshots are retained in ignored .tmp/release-stabilization; no merge or release is performed by this lane. This plan stays open for coordinator integration, rather than claiming the changes shipped. diff --git a/devlog/_plan/261003_release_lane_c/000_plan.md b/devlog/_plan/261003_release_lane_c/000_plan.md new file mode 100644 index 00000000000..863565a31ec --- /dev/null +++ b/devlog/_plan/261003_release_lane_c/000_plan.md @@ -0,0 +1,47 @@ +# Preserve discovered Antigravity routing and complete Ollama replay batches + +Lane C will preserve the already-landed Antigravity discovery and snapshot contracts, carry only evidence-supported remaining Claude 5.5 behavior, and repair Ollama commentary replay in a separate PR. The reported Antigravity 404 has not yet been causally reproduced. Coordinator ROADMAP LOCKED was received after its docs-only cycle at 2152fdfeb8; local reflection and independent audit still precede implementation. + +Reader: the release coordinator deciding which independently reviewed changes are safe to integrate; source PRs and release authority remain with the coordinator. + +## Loop contract + +- Archetype/trigger: satisfy-spec, delegated release-stabilization Lane C; C3 provider contracts, C4 care for security-boundary review. +- Goal: attributed independent PRs with successful applicable exact-head CI, resolved valid reviews, and an honest source-disposition report. +- Non-goals: merge/release/source-PR closure, contributor-branch rewrites, installed-app replacement, account/credential mutation, new paid resources, guessed upstream IDs or automatic generation migration. +- Verifier: focused tests named in each decade doc; typecheck, privacy, structure, layout/file-size; applicable PR CI at its current head. Runtime checks are NOT RUN during the initial P/A/docs gate. Package scripts inspected at package.json:50-59; explicit test paths below observe changed behavior. Prose quality remains independent human/agent review, not a runtime-test claim. +- Stop: DONE only after safe changes are published, checks genuinely pass, reviews resolve and full handoff exists. NOOP requires fresh proof content already landed. Missing upstream evidence is a reported limitation and may prevent issue-resolution claims; unresolved unsafe routing is BLOCKED/NEEDS_HUMAN, not fixed. No token/time budget was set, so BUDGET_EXHAUSTED cannot be inferred from waiting. +- Memory/evidence: this unit; ignored .tmp/release-stabilization/report.md and receipt logs. Unpublished security notes belong only in ignored scratch. +- Escalation: new write ownership, destructive operations, missing upstream evidence needed for static routing, or unresolved maintainer objection. Continue independent Ollama work if Antigravity has an external blocker. +- Resources: existing repository tools/GitHub identity; post-unlock bounded provider probes using existing intended credentials only, no refresh/config mutation and no credential output. No user token/cost/wall-clock cap. Commands use bounded process handles. + +## Dependency map and publication + +1. roadmap: docs-only proposal, same-architect reflection, independent A audit, explicit coordinator unlock, docs-only B/C/D. +2. antigravity: consume 010; revalidate #6501/current dev and source #6497, collect bounded upstream evidence, implement the audited safe subset, run its ordinary independent code/security review and focused verification, then publish the coherent slice on codex/release-261003-c with its own required PR CI. +3. ollama: consume 020 independently of Antigravity runtime; execute sequentially in this worktree on codex/release-261003-c-ollama cut from fresh dev, preserving both branch heads. Publish after its own independent review and verification without waiting for the other slice. No parallel branch-changing subagents. +4. handoff: consume 030; collect each published slice's exact-head CI and resolved review evidence, then give the coordinator both heads/source dispositions. The coordinator owns serial merges, lane=all CI, and the full parallel adversarial phase on its integrated candidate before deployment. + +These are independent PRs, not a native stack. Both source PRs currently target dev in contributor forks; successful stack API inspection returned [] for each. + +## Evidence and consultation + +Baseline: origin/dev cb2d1736a858a69b3d95944d531f48b93c2f56a5; #6501 merged as 3bae88cce7400e47a5e69f9fd28749ed25f919f6. #6497 head 742bd6fa33f89fc66d112bafc93bf6753c62a52f; #6509 head de5bb1f215c613670e03585e918a344c510ba97a. #6502 remains open and reports a saved suffix 404 without a wire trace. Source JSON and review snapshots are in ignored scratch. + +V1 architect handle 01a1020f-0150-7a62-ba3e-e2b7bad4494a used a fresh context with inherited model/effort; no model-family independence claimed. Proposal D1-D6 accepted with D2 amended: static 5.5 routing is excluded from the executable patch until a read-only bounded probe supplies exact destination-specific wire evidence and a revised plan is reflected/audited. D3 holds 4.6 migration; D4 carries deterministic usage normalization and derived cost attribution; D5 preserves the local pending-batch owner; D6 expands negative and immutability coverage. Proposal cited the earlier base b82b39018b; main rechecked the relevant unchanged spans after moving to cb2d1736a8. + +Alternatives rejected: wholesale #6497 cherry-pick conflicts with landed discovery and changes saved generation/effort without adequate evidence; generic suffix stripping invents historical usage mappings; one combined PR imposes a false dependency; global history reorder weakens per-batch validation. + +Same-architect reflection: ALIGNED D1-D6, no material design gaps. The independent roadmap audit subsequently passed; implementation/publication evidence is recorded in each PR's Verification section. + +Coordinator gate: the final parallel inherited-subagent regression phase applies to the integrated candidate before deployment. Each lane slice retains its ordinary independent review, local verification and exact-head PR CI. ROADMAP LOCKED released the initial runtime/push/CI gate; no merge/release authority was added. + +Vendor-price evidence opened 2026-10-03: https://platform.claude.com/docs/en/about-claude/pricing lists Sonnet 5.5 input/output/cache-read/5-minute-cache-write 2/10/0.2/2.5 and Opus 5.5 4/20/0.2/5 USD/MTok. This supports underlying reference prices only, not CCA billing. Initial baseline test could not load zod/v4; frozen-lockfile dependency install with lifecycle scripts disabled completed without a lockfile change. + +Post-unlock bounded native evidence: configured daily-cloudcode endpoint returned HTTP 200 discovery with six Claude 5.5 low/medium/high IDs, each maxTokens 1,000,000 and image support. Current unmodified adapter returned HTTP 200/candidates for saved claude-sonnet-5-5-high with conflicting low effort; wire remained high and no thinkingConfig was sent. No token refresh, account writes or installed-app replacement. This single-account evidence cannot establish the original Windows/multi-account cause. #6497 static metadata of 250,000 differs from this live response; no static routing or metadata carry is justified for the observed working path. + +The roadmap audit accepted the explicit eight-row membership assertion update (baseline 152 to 160). Its initial publication hold was superseded by the coordinator's integrated-candidate clarification; the dependency map above states the operative workflow. + +Roadmap A round 2: independent reviewer 01a10216-e0dd-7dc1-8e81-805e9fe2cfcb PASS, no blockers. Fresh-reader check understood safe scope and remaining verification boundaries. Docs-only B finalized this roadmap; next cycle executes 010, preserving current routing. Baselines: usage-cost 102 pass; Ollama native 26 pass; reviewer independently ran discovered families 9 pass. Ordinary bun-run gates exposed the skipped local Bun package postinstall; use the installed Bun binary directly for equivalent checker entrypoints, preserving dependencies and global install. + +Each independently reviewed and locally verified slice may publish immediately and run its own PR CI. Antigravity is not delayed behind Ollama; the lane reports evidence incrementally. diff --git a/devlog/_plan/261003_release_lane_c/010_antigravity.md b/devlog/_plan/261003_release_lane_c/010_antigravity.md new file mode 100644 index 00000000000..f1ea3890f06 --- /dev/null +++ b/devlog/_plan/261003_release_lane_c/010_antigravity.md @@ -0,0 +1,124 @@ +# Claude 5.5 usage attribution while preserving discovered wire routing + +Depends on: audited roadmap and explicit ROADMAP LOCKED. Revalidate current dev and #6497 before B. No new public type/enum or persistence field is introduced. + +## Exact file map + +MODIFY src/providers/antigravity-models.ts only in ANTIGRAVITY_USAGE_BASE_BY_ID (currently :754-775). Preserve discoveredAntigravityEffortWireModelId precedence (:667), snapshot scoping, partial-family behavior and existing 4.6 IDs. Add only deterministic known Claude 5.5 base/tier usage identities before return rev: + +```diff ++ for (const base of ["claude-sonnet-5-5", "claude-opus-5-5"]) { ++ rev[base] = base; ++ for (const effort of ANTIGRAVITY_DISCOVERY_EFFORTS) rev[`${base}-${effort}`] = base; ++ } + return rev; +``` + +MODIFY src/usage/expected-prices.ts after existing Antigravity 4.6 rows (:367-374): adapt the eight 5.5 rows from #6497, retaining existing CLAUDE_SONNET_55/CLAUDE_OPUS_55 constants; ALL eight status fields are verified-derived. Source text says derived Anthropic reference price, not CCA billing. Verify the cited underlying vendor price before carrying a fresh verifiedAt date; do not invent current-price evidence. Preserve 4.6 historical overlay rows. + +MODIFY tests/usage/usage-cost.test.ts membership assertion at :466 from 152 to 160 and include all eight new keys in its reviewed expected set, then adjacent existing Claude 5.5 price cases: add google-antigravity derived source assertions for the base and each tier with literal expected tuples (Sonnet 2/10/0.2/2.5; Opus 4/20/0.2/5, confirmed in the official pricing table on 2026-10-03). Assert 4.6 identity/cost unchanged and unknown future suffix identity unchanged; use tests/usage/usage-summary.test.ts if durable aggregation-level coverage is missing. Check file-size cap before adding; if at cap use NEW tests/usage/usage-antigravity-55.test.ts and register it in both scripts/test-layout/layout.json and tests/fixtures/test-layout-expected.json with the existing usage domain shape. No lowered caps/skips. + +MODIFY structure/providers-and-adapters.md :557-559: append that deterministic known 5.5 usage normalization is independent of discovery and does not migrate saved routing. MODIFY docs-site/src/content/docs/guides/providers.md at Antigravity guidance: reference costs are derived estimates; use discovered models/efforts; do not claim #6502 resolved without a successful matching request. Review all manifest owners for src/providers and src/usage; amend only affected prose. + +## Routing evidence decision before implementation + +Inspect the existing intended Antigravity discovery path and credential availability without printing secrets. After unlock, bound model discovery and at most a minimal request for the reported saved Sonnet 5.5-high selection; retain only wire IDs/effort/status and redacted endpoint identity. No account configuration or installed-service restart. If credentials are absent or discovery/request fails, record that exact limitation and leave #6502 unresolved. Existing #6501 controlled fixture proof does not prove live entitlement or backend availability. + +Static 5.5 fallback, 4.6 retirement, inferred context/image metadata, and cross-generation aliases are NOT in the patch above. Adding static fallback requires evidence plus P/A amendment with exact maps/tests. That amendment must cover medium default, xhigh/max/ultra clamp, suffix override, complete/partial/empty discovery and snapshot invalidation using an unbundled family so a new fallback cannot make invalidation coverage tautological. + +## Acceptance and commands (planned; NOT RUN at initial gate) + +- All known 5.5 base/low/medium/high usage spellings normalize deterministically without requiring registration; unknown model suffixes remain exact; historical 4.6 stays itself. Both src/usage/cost.ts and src/usage/summary.ts consume canonicalAntigravityUsageModel. src/adapters/google.ts:95 also calls it for a Gemini-only rejection predicate; Claude normalization must leave that behavior unchanged. +- Exact discovered family remains authoritative; explicit high plus conflicting low effort yields high wire and no thinkingConfig. Complete/partial/empty discovery, restart/outage and home/destination/generation isolation keep existing tests green. +- Eight reference-price rows carry verified-derived; literal tuple assertions and a source assertion prove provenance. No charge/billing claim. +- Run each affected file in its own Bun process: tests/adapters/google/antigravity-discovered-families.test.ts, tests/adapters/google/google-antigravity-wire.test.ts, tests/adapters/google/antigravity-static-catalog.test.ts, tests/providers/provider-antigravity-effort-families.test.ts, tests/providers/provider-antigravity-family-catalog.test.ts, tests/providers/provider-antigravity-wire-snapshot.test.ts, tests/usage/usage-cost.test.ts, tests/usage/usage-summary.test.ts, tests/providers/provider-registry-parity.test.ts. +- Run bun run typecheck; bun run privacy:scan; bun run structure:check; focused layout and file-size guards; bun run test:changed after examining its scope. Full local suite exception: concurrent stabilization worktrees share resources; record focused commands/counts and remaining platform/CI coverage in PR. +- Independent implementation/security review, actual applicable exact-head PR CI and maintained source credit precede review readiness. + +Credit for adapted source: Co-authored-by: Prince . Source #6497 stays open; report the withheld routing/migration delta explicitly. + +## Execution revalidation + +Previous D concluded: docs-only roadmap complete at 12d0ba48d0; next execute 010 usage/pricing only and preserve current routing. This cycle follows that direction. Live discovery/inference at the baseline supports retaining routing, and official vendor pricing supports the two reference tuples. Baseline usage-cost 102/0; direct installed-Bun tsc/structure/privacy all exit0. usage-cost is uncapped, but usage-summary is exactly at its 2069-line cap. Add the already-planned tests/usage/usage-antigravity-55.test.ts sibling and both layout registrations; do not edit usage-summary. Coordinator clarified that its own integrated candidate gets final parallel review; this slice may publish after scoped independent review and local verification. + +Focused architect 01a1021d-d929-7f41-bad4-e49df00f9633 D1-D6 accepted. Add estimateRequestCost estimated=true assertion, pool provider pricing, required top-level/per-day grouping with literal request/token/cost totals, and unchanged historical/unknown identities. Also update the pricing paragraph in structure/dashboard-and-usage.md. No wire changes. New sibling uses existing summarizeUsage and PersistedUsageEntry interfaces; for each family, low/medium/high rows plus one base row with a resolved high tier produce four requests per family, 4400 tokens and Sonnet 0.012 / Opus 0.024 reference cost at 1000 input+100 output each. Both aggregate and per-day rows must match those literals. Historical 4.6 and unknown future suffix rows remain separate. + +## Complete new sibling test content + +NEW tests/usage/usage-antigravity-55.test.ts (the existing cost file changes only membership; focused new assertions live together here): + +```typescript +import { describe, expect, test } from "bun:test"; +import { canonicalAntigravityUsageModel } from "../../src/providers/antigravity-models"; +import { estimateRequestCost, resolveMatchedPrice } from "../../src/usage/cost"; +import { findExpectedPriceOverlay } from "../../src/usage/expected-prices"; +import type { PersistedUsageEntry } from "../../src/usage/log"; +import { summarizeUsage } from "../../src/usage/summary"; + +const families = [ + { base: "claude-sonnet-5-5", cost4: { input: 2, output: 10, cacheRead: 0.2, cacheWrite: 2.5 }, total: 0.012 }, + { base: "claude-opus-5-5", cost4: { input: 4, output: 20, cacheRead: 0.2, cacheWrite: 5 }, total: 0.024 }, +]; +const now = Date.UTC(2026, 9, 3, 12); + +function row(model: string, index: number, resolvedModel?: string): PersistedUsageEntry { + return { + requestId: `antigravity-fixture-${index}`, timestamp: now - index, + provider: "google-antigravity", model, ...(resolvedModel ? { resolvedModel } : {}), + status: 200, durationMs: 1, usageStatus: "reported", + usage: { inputTokens: 1000, outputTokens: 100 }, totalTokens: 1100, + }; +} + +describe("Antigravity Claude 5.5 usage", () => { + for (const { base, cost4, total } of families) { + test(`${base} has deterministic identity and derived reference prices without discovery`, () => { + for (const id of [base, `${base}-low`, `${base}-medium`, `${base}-high`]) { + expect(canonicalAntigravityUsageModel(id)).toBe(base); + expect(findExpectedPriceOverlay("google-antigravity", id)).toMatchObject({ + provider: "google-antigravity", modelId: id, cost4, status: "verified-derived", + }); + for (const provider of ["google-antigravity", "google-antigravity-pabcdef"]) { + const price = resolveMatchedPrice(provider, id); + expect(price).toMatchObject({ cost4, status: "verified-derived", source: "expected" }); + expect(price?.sourceRef).toContain("derived:"); + expect(price?.sourceRef).toContain("platform.claude.com/docs/en/about-claude/pricing"); + const estimate = estimateRequestCost({ provider, model: id, usageStatus: "reported", usage: { inputTokens: 1000, outputTokens: 100 } }); + expect(estimate?.estimated).toBe(true); + expect(estimate?.cost.total).toBeCloseTo(total / 4, 10); + } + } + }); + + test(`${base} aggregates tiers and resolved IDs in model and day summaries`, () => { + const entries = [row(`${base}-low`, 1), row(`${base}-medium`, 2), row(`${base}-high`, 3), row(base, 4, `${base}-high`)]; + const summary = summarizeUsage(entries, "all", now); + expect(summary.models).toHaveLength(1); + expect(summary.models[0]).toMatchObject({ provider: "google-antigravity", model: base, requests: 4, totalTokens: 4400 }); + expect(summary.models[0]?.resolvedModel).toBeUndefined(); + expect(summary.models[0]?.estimatedCostUsd).toBeCloseTo(total, 10); + const days = summary.days.filter(day => day.requests > 0); + expect(days).toHaveLength(1); + expect(days[0]?.models).toHaveLength(1); + expect(days[0]?.models[0]).toMatchObject({ model: base, requests: 4, totalTokens: 4400 }); + expect(days[0]?.models[0]?.estimatedCostUsd).toBeCloseTo(total, 10); + expect(summary.summary.estimatedCostUsd).toBeCloseTo(total, 10); + }); + } + + test("historical Claude and unknown suffixes retain their exact usage identities", () => { + const ids = ["claude-sonnet-4-6", "claude-opus-4-6-thinking", "claude-sonnet-6-0-high", "claude-sonnet-5-5-ultra"]; + for (const id of ids) expect(canonicalAntigravityUsageModel(id)).toBe(id); + const summary = summarizeUsage(ids.map((id, i) => row(id, i + 1)), "all", now); + expect(summary.models.map(model => model.model).sort()).toEqual([...ids].sort()); + expect(resolveMatchedPrice("google-antigravity", "claude-sonnet-4-6")?.cost4) + .toEqual({ input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 }); + expect(resolveMatchedPrice("google-antigravity", "claude-opus-4-6-thinking")?.cost4) + .toEqual({ input: 5, output: 25, cacheRead: 0.5, cacheWrite: 6.25 }); + }); +}); +``` + +Same-architect final reflection: ALIGNED D1-D6 after correcting fixture count to four; no material gaps. + +B fixture correction: summarizeUsage all-range deliberately includes a leading empty day (dayCountForAllRange uses ceil(delta/day)+1). Assert exactly one nonempty day and its full model totals; no production change for an incorrect fixture assumption. New tests first failed four behavior cases before the runtime change, then all five passed after the mapping/overlays and this calendar-independent assertion. diff --git a/devlog/_plan/261003_release_lane_c/020_ollama.md b/devlog/_plan/261003_release_lane_c/020_ollama.md new file mode 100644 index 00000000000..bafe97fbb90 --- /dev/null +++ b/devlog/_plan/261003_release_lane_c/020_ollama.md @@ -0,0 +1,304 @@ +# Keep unresolved native batches open across commentary + +Depends on: roadmap/unlock only; runtime independent of 010. Carry #6509 at de5bb1f215c613670e03585e918a344c510ba97a on a separate own branch from current dev. + +MODIFY the four files in the exact proposed source diff below, adapting only current-base conflicts. PendingToolBatch.unresolvedCount is created from wireCalls.length, decremented only after validated result attachment and consumed only by the no-new-call assistant deferral branch. It is request-local internal state: serialization/deserialization N/A; no persisted or public API field. + +The new branch activates with an open batch, unresolvedCount > 0, assistant role and no extracted calls. It defers assistant text/thinking until flushPending emits genuine results in original call order and then releaseDeferred emits conversation arrival order. New tool-call batches and EOF settle missing results with existing unknown markers. Fully resolved batches flush before normal assistant turns. Validation remains before decrement: unknown/duplicate/name/namespace results reject; invalid/reused call IDs retain their existing rules. No parsed input mutation. + +## Negative verification delta beyond source PR + +MODIFY tests/providers/ollama/ollama-native.test.ts (the existing uncapped file; no additional registry entry is needed): construct batches with commentary interleaved before orphan IDs, duplicate result IDs, mismatched tool names and mismatched namespaces; each must throw the existing error. Add old-batch result after a new tool-call batch (must reject), unresolved EOF (must emit exactly one unknown result before commentary), fully resolved result then commentary (must not defer past subsequent conversation), reverse-arrival results in a multi-call batch (must output call order), and deep-frozen parsed context with nested content/call argument objects (must build unchanged). Assert output payload order and values rather than internal counters. Keep all existing malformed-input tests. + +Commands planned, NOT RUN under initial gate: bun test tests/providers/ollama/ollama-native.test.ts; native-parser/native-v4/native-reasoning-wire/native-structured-output files in separate Bun processes; tests/adapters/adapter-tool-conformance.test.ts and adapter-buffered-tool-conformance.test.ts; typecheck/privacy/structure/layout/file-size/test:changed gates. Document full-local-suite exception for concurrent worktrees. No live Ollama service is assumed; absence is an explicit evidence limit. + +Review all src/adapters ownership docs; preserve OpenAI behavior and touch only the Ollama paragraph in chat-compat. User docs describe observed contract, never promise caller-side physical tool execution. Independent code/security review and applicable exact-head CI are required. Source #6509 stays open. + +Credit: Co-authored-by: potota90 <85318310+adtumk@users.noreply.github.com>. + +## Source-PR snapshot and final regression evidence + +The embedded diff below records original #6509, before the carry's additional adversarial tests. Its JSON snapshot assertion is not the deep-freeze proof. The implemented obligation lives in separate [PR #6519](https://github.com/lidge-jun/opencodex/pull/6519): [deep-frozen replay regression at commit 917fa41229, line 589](https://github.com/lidge-jun/opencodex/blob/917fa4122977b7d890ba1115724e2b5a9a7940cd/tests/providers/ollama/ollama-native.test.ts#L589). That test recursively freezes the entire parsed request, including content arrays and the non-empty `{ cmd: "pwd" }` call arguments, before building. It verifies unchanged input plus output call/deferred ordering, thinking and argument values. The ten additional cases and all 42 native tests passed there. No Ollama runtime or test changes are included in the Antigravity pricing PR. + +## Original source diff (before carry additions) + +```diff +diff --git a/docs-site/src/content/docs/reference/adapters.md b/docs-site/src/content/docs/reference/adapters.md +index a765419710..c616f58a0e 100644 +--- a/docs-site/src/content/docs/reference/adapters.md ++++ b/docs-site/src/content/docs/reference/adapters.md +@@ -118,6 +118,10 @@ be configured on a separately named custom or self-hosted Ollama provider with + is refused rather than mis-sent, and remote image URLs are not fetched. + - **Tools:** declared in Ollama's native shape, streamed tool calls are whole-call records with + object-valued `arguments`, and tool-result replay is paired strictly by call id and tool name. ++ Codex may record assistant commentary before a pending call's results. Text/thinking with no ++ new tool calls is deferred until the batch is settled, so genuine results remain beside their ++ originating calls. A new tool-call batch still settles the preceding one; missing results retain ++ an explicit unknown-status marker, and orphan or duplicate results remain invalid. + `tool_choice: "none"` and `auto` behave normally; **`required` or an exact named choice fails + closed**, because Ollama's `/api/chat` has no `tool_choice` field to enforce it with. + - **Structured output is refused on canonical Ollama Cloud.** Ollama currently documents structured +diff --git a/src/adapters/ollama-native.ts b/src/adapters/ollama-native.ts +index 2f95c5dbdc..b180d1f1d5 100644 +--- a/src/adapters/ollama-native.ts ++++ b/src/adapters/ollama-native.ts +@@ -77,6 +77,7 @@ interface PendingToolCall { + interface PendingToolBatch { + calls: PendingToolCall[]; + byId: Map; ++ unresolvedCount: number; + } + + interface NativeStreamToolCall { +@@ -302,6 +303,12 @@ function assistantTextThinkingAndCalls(message: OcxAssistantMessage): { + }; + } + ++/** ++ * Build native replay messages without mutating the parsed history. Keep tool results ++ * beside their originating batch, deferring intervening conversation until settlement. ++ * Reserve replayed call IDs for response translation and mark missing results explicitly. ++ * @throws When call IDs are invalid or results are orphaned, duplicated, or mismatched. ++ */ + function buildNativeMessages( + parsed: OcxParsedRequest, + reservedToolCallIds: Set, +@@ -323,12 +330,14 @@ function buildNativeMessages( + // early. The openai-chat adapter defers them the same way; refusing the replay killed the turn. + let deferred: OllamaNativeMessage[] = []; + ++ /** Emit held conversation messages in their recorded arrival order after settlement. */ + const releaseDeferred = (): void => { + if (deferred.length === 0) return; + messages.push(...deferred); + deferred = []; + }; + ++ /** Settle the open batch with genuine results or unknown markers, then release deferred messages. */ + const flushPending = (): void => { + if (!pending) return; + for (const call of pending.calls) { +@@ -376,13 +385,14 @@ function buildNativeMessages( + throw new Error(`ollama-native tool result ${message.toolCallId} names the wrong originating tool`); + } + call.result = message; ++ pending.unresolvedCount--; + continue; + } + + // Native Ollama requires the whole assistant tool-call turn followed by its tool results. A + // conversational message that arrives while the batch is still open is held aside instead of + // closing it, so the call keeps its results adjacent; it is released right after the batch +- // flushes. Anything else (a new assistant turn) settles the batch first. ++ // flushes. A new assistant tool-call batch settles the preceding batch first. + if (pending) { + if (message.role === "user" || message.role === "developer") { + const translated = message.role === "user" +@@ -393,6 +403,19 @@ function buildNativeMessages( + : { role: "system", content: translated.content }); + continue; + } ++ if (message.role === "assistant" && pending.unresolvedCount > 0) { ++ const extracted = assistantTextThinkingAndCalls(message); ++ if (extracted.calls.length === 0) { ++ // Commentary can follow the call items but precede their recorded results. ++ // Keep the batch open and release its text/thinking after the actual results. ++ deferred.push({ ++ role: "assistant", ++ content: extracted.content, ++ ...(extracted.thinking ? { thinking: extracted.thinking } : {}), ++ }); ++ continue; ++ } ++ } + flushPending(); + } + +@@ -443,7 +466,11 @@ function buildNativeMessages( + }; + messages.push(native); + if (wireCalls.length > 0) { +- pending = { calls: wireCalls, byId: new Map(wireCalls.map(call => [call.id, call])) }; ++ pending = { ++ calls: wireCalls, ++ byId: new Map(wireCalls.map(call => [call.id, call])), ++ unresolvedCount: wireCalls.length, ++ }; + } + break; + } +diff --git a/structure/providers/chat-compat.md b/structure/providers/chat-compat.md +index a469a3c8ef..740bb7a115 100644 +--- a/structure/providers/chat-compat.md ++++ b/structure/providers/chat-compat.md +@@ -122,13 +122,13 @@ and synthesizing explicit "no tool result was recorded" answers only when no rea + (Kimi/Moonshot 400 `ocx-mrqaiw05-269`; unit `devlog/_fin/260718_dangling_toolcall_hardening`). + + The native Ollama wire carries the same contract. `src/adapters/ollama-native.ts` +-`buildNativeMessages` defers `user`/`developer` messages that arrive while a batch is open and +-releases them after the tool messages, and answers a call with no result anywhere in the replayed +-history with the same `[ocx] no tool result was recorded for ""` marker. The shape it +-absorbs is ordinary Codex history, not a malformed one: Codex records mid-turn items (a +-`PostToolUse` hook verdict, a context notice) between an assistant `tool_calls` message and that +-call's own result. The strict pair checks (orphan result, duplicate result, result naming another +-tool) still throw on both wires (#4842). ++`buildNativeMessages` defers `user`/`developer` messages while a batch is open; assistant text/thinking ++with no new tool calls is also deferred while results remain outstanding. Deferred messages retain ++arrival order after recorded results. An unresolved counter avoids rescanning calls per message. ++A new tool-call batch settles its predecessor; completed batches keep subsequent messages in place. ++Codex can record hook notices or commentary between calls and results. Missing results keep the ++`[ocx] no tool result was recorded for ""` marker; orphan IDs, duplicates, and mismatched names ++still throw (#4842). `tests/providers/ollama/ollama-native.test.ts` covers replay and compaction. + + Forward-mode OpenAI passthrough also repairs replayed `call_id` values longer than the Responses + API's 64-character limit. Sidechat/fork replay can namespace routed-provider ids beyond that limit, +diff --git a/tests/providers/ollama/ollama-native.test.ts b/tests/providers/ollama/ollama-native.test.ts +index 44a09e8a8c..c17e5c1967 100644 +--- a/tests/providers/ollama/ollama-native.test.ts ++++ b/tests/providers/ollama/ollama-native.test.ts +@@ -9,12 +9,14 @@ import { getProviderRegistryEntry } from "../../../src/providers/registry"; + import { withStubbedProviderFetch } from "../../helpers/catalog-provider-fetch"; + import type { OcxParsedRequest, OcxProviderConfig } from "../../../src/types"; + ++/** Discover routed test models with provider fetches stubbed to avoid live requests. */ + const gatherRoutedModels: typeof gatherRoutedModelsDirect = (config, options) => + gatherRoutedModelsDirect(withStubbedProviderFetch(config), options); + + /** The four ids this transport is maintained against. */ + const TARGETS = ["glm-5.3-flash", "deepseek-v4-flash:0731", "glm-5.2", "kimi-k3"] as const; + ++/** Configure a native Ollama test provider with inert credentials and caller overrides. */ + function ollamaProvider(overrides: Partial = {}): OcxProviderConfig { + return { + adapter: "ollama-native", +@@ -28,6 +30,7 @@ function ollamaProvider(overrides: Partial = {}): OcxProvider + } as OcxProviderConfig; + } + ++/** Build a minimal streamed replay request from synthetic messages and caller options. */ + function parsedWith( + messages: unknown[], + options: Record = {}, +@@ -285,6 +288,120 @@ describe("ollama-native — request shape", () => { + expect(messages[3].content).toBe("[hook] design findings requiring review"); + }); + ++ test("assistant commentary before parallel results keeps the original batch open", async () => { ++ const adapter = createOllamaNativeAdapter(ollamaProvider()); ++ const { body } = await adapter.buildRequest(parsedWith([ ++ { ++ role: "assistant", ++ content: [ ++ { type: "toolCall", id: "call_commentary_first", name: "exec", arguments: { input: "text('first')" } }, ++ { type: "toolCall", id: "call_commentary_second", name: "exec", arguments: { input: "text('second')" } }, ++ ], ++ timestamp: 1, ++ }, ++ { role: "assistant", content: [{ type: "text", text: "checking both results" }], timestamp: 2 }, ++ { role: "toolResult", toolCallId: "call_commentary_second", toolName: "exec", content: "second result", isError: false, timestamp: 3 }, ++ { role: "toolResult", toolCallId: "call_commentary_first", toolName: "exec", content: "first result", isError: false, timestamp: 4 }, ++ ])); ++ const messages = JSON.parse(String(body)).messages; ++ expect(messages.map((message: { role: string }) => message.role)) ++ .toEqual(["assistant", "tool", "tool", "assistant"]); ++ expect(messages[1]).toMatchObject({ tool_call_id: "call_commentary_first", content: "first result" }); ++ expect(messages[2]).toMatchObject({ tool_call_id: "call_commentary_second", content: "second result" }); ++ expect(messages[3].content).toBe("checking both results"); ++ }); ++ ++ test("assistant commentary after one parallel result preserves the remaining genuine result", async () => { ++ const adapter = createOllamaNativeAdapter(ollamaProvider()); ++ const { body } = await adapter.buildRequest(parsedWith([ ++ { ++ role: "assistant", ++ content: [ ++ { type: "toolCall", id: "call_partial_first", name: "exec", arguments: {} }, ++ { type: "toolCall", id: "call_partial_second", name: "exec", arguments: {} }, ++ ], ++ timestamp: 1, ++ }, ++ { role: "toolResult", toolCallId: "call_partial_first", toolName: "exec", content: "first done", isError: false, timestamp: 2 }, ++ { role: "assistant", content: [{ type: "text", text: "waiting for the second result" }], timestamp: 3 }, ++ { role: "toolResult", toolCallId: "call_partial_second", toolName: "exec", content: "second done", isError: false, timestamp: 4 }, ++ ])); ++ const messages = JSON.parse(String(body)).messages; ++ expect(messages.map((message: { role: string }) => message.role)) ++ .toEqual(["assistant", "tool", "tool", "assistant"]); ++ expect(messages[1].content).toBe("first done"); ++ expect(messages[2].content).toBe("second done"); ++ expect(messages[3].content).toBe("waiting for the second result"); ++ }); ++ ++ test("deferred assistant text and thinking retain their order without mutating parsed history", async () => { ++ const parsed = parsedWith([ ++ { role: "assistant", content: [{ type: "toolCall", id: "call_thinking", name: "exec", arguments: {} }], timestamp: 1 }, ++ { role: "developer", content: "context notice", timestamp: 2 }, ++ { role: "assistant", content: [{ type: "text", text: "first comment" }, { type: "thinking", thinking: "synthetic reasoning" }], timestamp: 3 }, ++ { role: "assistant", content: [{ type: "text", text: "second comment" }], timestamp: 4 }, ++ { role: "toolResult", toolCallId: "call_thinking", toolName: "exec", content: "recorded result", isError: false, timestamp: 5 }, ++ ]); ++ const original = JSON.stringify(parsed); ++ const { body } = await createOllamaNativeAdapter(ollamaProvider()).buildRequest(parsed); ++ const messages = JSON.parse(String(body)).messages; ++ expect(messages.map((message: { role: string }) => message.role)) ++ .toEqual(["assistant", "tool", "system", "assistant", "assistant"]); ++ expect(messages[1].content).toBe("recorded result"); ++ expect(messages[2].content).toBe("context notice"); ++ expect(messages[3]).toMatchObject({ content: "first comment", thinking: "synthetic reasoning" }); ++ expect(messages[4].content).toBe("second comment"); ++ expect(JSON.stringify(parsed)).toBe(original); ++ }); ++ ++ test("assistant commentary after a complete batch keeps its existing wire position", async () => { ++ const { body } = await createOllamaNativeAdapter(ollamaProvider()).buildRequest(parsedWith([ ++ { role: "assistant", content: [{ type: "toolCall", id: "call_complete", name: "exec", arguments: {} }], timestamp: 1 }, ++ { role: "toolResult", toolCallId: "call_complete", toolName: "exec", content: "done", isError: false, timestamp: 2 }, ++ { role: "assistant", content: [{ type: "text", text: "completed comment" }], timestamp: 3 }, ++ { role: "user", content: "next turn", timestamp: 4 }, ++ ])); ++ const messages = JSON.parse(String(body)).messages; ++ expect(messages.map((message: { role: string }) => message.role)) ++ .toEqual(["assistant", "tool", "assistant", "user"]); ++ expect(messages[1].content).toBe("done"); ++ expect(messages[2].content).toBe("completed comment"); ++ expect(messages[3].content).toBe("next turn"); ++ }); ++ ++ test("a new tool-call batch still settles an unresolved batch after assistant commentary", async () => { ++ const { body } = await createOllamaNativeAdapter(ollamaProvider()).buildRequest(parsedWith([ ++ { role: "assistant", content: [{ type: "toolCall", id: "call_unfinished", name: "exec", arguments: {} }], timestamp: 1 }, ++ { role: "assistant", content: [{ type: "text", text: "before the next batch" }], timestamp: 2 }, ++ { role: "assistant", content: [{ type: "toolCall", id: "call_next", name: "exec", arguments: {} }], timestamp: 3 }, ++ { role: "toolResult", toolCallId: "call_next", toolName: "exec", content: "next result", isError: false, timestamp: 4 }, ++ ])); ++ const messages = JSON.parse(String(body)).messages; ++ expect(messages.map((message: { role: string }) => message.role)) ++ .toEqual(["assistant", "tool", "assistant", "assistant", "tool"]); ++ expect(messages[1].tool_call_id).toBe("call_unfinished"); ++ expect(messages[1].content).toContain("execution status unknown"); ++ expect(messages[2].content).toBe("before the next batch"); ++ expect(messages[4]).toMatchObject({ tool_call_id: "call_next", content: "next result" }); ++ }); ++ ++ test("routed compaction preserves a result recorded after assistant commentary", async () => { ++ const parsed = parsedWith([ ++ { role: "assistant", content: [{ type: "toolCall", id: "call_compaction", name: "exec", arguments: {} }], timestamp: 1 }, ++ { role: "assistant", content: [{ type: "text", text: "commentary before compaction" }], timestamp: 2 }, ++ { role: "toolResult", toolCallId: "call_compaction", toolName: "exec", content: "genuine result", isError: false, timestamp: 3 }, ++ { role: "user", content: "summarize this history", timestamp: 4 }, ++ ], { toolChoice: "none" }); ++ parsed._compactionRequest = true; ++ const { body } = await createOllamaNativeAdapter(ollamaProvider()).buildRequest(parsed); ++ const request = JSON.parse(String(body)); ++ expect(request.messages.map((message: { role: string }) => message.role)) ++ .toEqual(["assistant", "tool", "assistant", "user"]); ++ expect(request.messages[1]).toMatchObject({ tool_call_id: "call_compaction", content: "genuine result" }); ++ expect(request.messages[2].content).toBe("commentary before compaction"); ++ expect(request.tools).toBeUndefined(); ++ }); ++ + test("a deferred user message keeps its text and images after the tool result", async () => { + const adapter = createOllamaNativeAdapter(ollamaProvider()); + const png = "data:image/png;base64,iVBORw0KGgo="; + +``` diff --git a/devlog/_plan/261003_release_lane_c/030_handoff.md b/devlog/_plan/261003_release_lane_c/030_handoff.md new file mode 100644 index 00000000000..f505769c615 --- /dev/null +++ b/devlog/_plan/261003_release_lane_c/030_handoff.md @@ -0,0 +1,13 @@ +# Deliver two independently verified heads to the coordinator + +Depends on: both implementation outcomes, including an explicitly unresolved Antigravity live issue when access prevents causal verification. MODIFY this unit with each cycle conclusion and actual evidence; NEW ignored .tmp/release-stabilization/report.md with exact own branch/head and PR URL, source heads and dispositions, local test commands/counts, hosted event/head/check SHA/run ID/attempt/conclusion, review outcomes and native limits. No runtime changes in this phase. + +Each slice publishes after its ordinary independent code/security review and focused verification; main resolves valid findings and re-verifies that slice. Publication does not wait for the other slice. The coordinator owns the separate full parallel adversarial regression phase on the integrated candidate before deployment. This handoff collects both slices' evidence after their independent publication. + +Before publishing, fetch current dev/source heads and compare patch IDs/changed paths to avoid duplicate landed content. Preserve source authors in Co-authored-by trailers; use every repository PR template section. Both ordinary PRs target dev, do not alter source branches or close source PRs. The first PR carries its own documentation; the second excludes unrelated Antigravity changes. Verify native stack membership read-only; never register one. + +At C, read expected workflow job conditions and compare to observed check-runs. Required tests must actually run successfully against the applicable head or its verified PR merge ref. Absent/pending/skipped/cancelled/older-head checks are not passing. Lane may dispatch only its required PR CI after ROADMAP LOCKED; coordinator owns lane=all and all merges. Do not cancel other lanes' jobs. Inspect failing logs before repairs/reruns and preserve outstanding maintainer objections. + +Before completion, review every source item: #6501 preserved; #6497 adapted/deferred chunks and credit; #6502 reproduced/fixed or unresolved with actual access/status evidence; #6509 adopted delta and added validation proof. A fresh reader must be able to identify safe integration heads and remaining blockers from the report. Unmet checks or unresolved material reviews prevent DONE. No user-set time/token cap; waiting is not exhaustion. + +Criterion c-4 collects both exact-head PR CI receipts and resolved reviews for final handoff; it does not impose a cross-slice publication prerequisite. diff --git a/devlog/_plan/261003_release_lane_d/000_plan.md b/devlog/_plan/261003_release_lane_d/000_plan.md new file mode 100644 index 00000000000..7668f49ea8b --- /dev/null +++ b/devlog/_plan/261003_release_lane_d/000_plan.md @@ -0,0 +1,22 @@ +# Child pairing release stabilization + +Public source: [PR #6076](https://github.com/lidge-jun/opencodex/pull/6076), which requires operator pairing before Child join and supplies standalone enrollment. This unit prepares that existing contribution for integration while keeping its authorship and review history. + +## Phase order + +1. Document scope and verification boundaries; independently review the roadmap. +2. Carry the contribution onto current `dev` and verify the affected pairing, session and link behavior. +3. Exercise isolated CLI mint, HTTP redemption, paired dashboard, Child join and restart; distinguish component tests from operational evidence. +4. Publish a focused pull request with independent review, applicable exact-head CI and a handoff to the integration coordinator. + +## Authority + +The lane uses its own `codex/release-261003-d` branch. It may commit, push that branch, prepare its pull request and run its required CI. Merging, releasing, source-PR closure, account or credential settings, branch protection and installed-app replacement remain outside this lane. + +## Verification boundaries + +Focused regression tests, typecheck, privacy and structure checks are required. Dashboard changes also need the relevant GUI tests, lint, build and rendered evidence. Concurrent release worktrees justify a documented full-local-suite exception; the exact commands, results and remaining hosted coverage belong in the PR. + +A source audit or synthetic join test does not establish native SSH enrollment or successful process replacement. Required CI must run successfully for the proposed head; absent, skipped, pending or cancelled checks are not passing evidence. Existing maintainer objections and independent security review remain explicit gates. The coordinator owns final integrated regression and serial integration. + +Detailed working evidence is kept in ignored `.tmp/release-stabilization/report.md`. Unpublished security investigation belongs only in ignored scratch space; this public record contains scope and verification boundaries, not findings or reproduction instructions. diff --git a/devlog/_plan/261003_release_stabilization/000_plan.md b/devlog/_plan/261003_release_stabilization/000_plan.md new file mode 100644 index 00000000000..46786cdd92d --- /dev/null +++ b/devlog/_plan/261003_release_stabilization/000_plan.md @@ -0,0 +1,77 @@ +# Stabilize the next production release + +The recovered train is integrated, but new request failures and pending account, +pairing and accounting reviews need disposition before publication. Five isolated +implementation lanes prepare reviewed changes; this coordinator lands ready PRs +serially, verifies the complete candidate, and publishes the next stable release. + +## Loop contract + +- Archetype: satisfy-spec, C4 integration/release; trigger: explicit maintainer request + to use inherited parallel worktree tasks and leaf subagents through deployment. +- Goal: address the listed scope, land safe changes progressively and verify production + publication. Each lane owns its goal/FSM; leaves own neither and cannot spawn. +- Non-goals: unrelated features, native GitHub stacks, changing branch protection, + new credentials, account preference changes, or replacing the operator's installed app. +- Verifier: focused activated regressions and exact-head required PR CI, then final + candidate lane=all CI, release-line push CI and actual publication/artifact checks. +- Stop: all five work phases have evidence and production deployment is verified. + Missing native evidence remains missing; closing an issue does not prove acceptance. +- Artifacts: this numbered unit, lane-owned numbered units, and ignored + `.tmp/release-stabilization/` receipts. Unpublished security notes stay in scratch. +- Outcomes: DONE only with verified release; unresolved gates remain open. Host blocked + status follows its recurrence rule; timeout/compaction is not a terminal result. +- Escalation: unavailable required access or policy conflict; no invented approval or + weakened verification. Main reclaims only failed/retired lane work with provenance. +- Resources: existing repository/GitHub access and configured environments only; no + new paid resources. No user token/time budget. Bounded commands, one CI observer, + focused local tests under concurrent-worktree resource exception; CI covers breadth. + +## Dependency order + +1. `roadmap`: docs-only proposal, main dispositions, architect reflection, independent + audit and document checks. Locks this ownership and acceptance map. +2. `stabilize`: execute [010](010_stabilization.md). Five worktree tasks prepare + implementation PRs with inherited leaf reviews. Main integrates serially as ready. +3. `regression`: execute [015](015_independent_regression.md), a separate PABCD cycle + with parallel independent inherited regression subagents before publication. +4. `candidate`: execute [020](020_candidate.md) after regression closure. +5. `publish`: execute [030](030_publication.md) after candidate acceptance. + +Lane P phases own exact patch plans based on the current source. This coordinator +roadmap owns their dispatch and integration interfaces, not speculative implementations +of defects not yet diagnosed. No lane enters B without its own audited diff-level plan. + +## Measured starting state + +At initial inspection the previous full CI was successful at `7c59baf9597fbad188c49bb562bdd4858275766d` +(run 37110109894). Dev subsequently advanced; the initial fetched baseline for this +unit is `b82b39018b48ad489110b4165ee2cd9ba30433d5`, including #6501, #6506 and #6498. +The version sources report 2.77.0; latest stable is v2.76.0. Re-read both before selecting +the publication version. The current branch is `codex/release-stabilization-261003`. + +Repository authorities: `MAINTAINERS.md`, `scripts/release.ts`, +`structure/ops/docs-and-release.md`, `.github/workflows/ci.yml`, +`.github/workflows/service-lifecycle.yml`, `.github/workflows/release.yml`. +Structure ownership remains with each implementation area; each lane updates its +matching structure and user documentation in the same implementation PR. + +## Consultation and continuity + +Architect (receipt retained in private coordination evidence) proposed ARC-01 through ARC-05. +Main accepts ARC-01 five lanes, ARC-02 B-before-E shared ownership, ARC-03 serial +integration with A preceding final E composition, ARC-04 native acceptance ownership, +and ARC-05 version pre-move and exact release-event proof. The same architect read +these four concrete documents and returned ALIGNED with ARC-01 through ARC-05, +with no remaining architectural gaps. Independent reviewer +(receipt retained in private coordination evidence) passed the original plan and ARC-06 supplement. +Its gate-recording clarification was folded into 010 and rechecked: VERDICT PASS, +no remaining blockers. No implementation or +release readiness is claimed by this roadmap. The preceding completed recovery unit +established integration readiness, explicitly not a publication receipt; this new unit +adds the user-authorized fixes, acceptance and publication. + +User steering added ARC-06: a separate independent parallel regression PABCD cycle. +The same architect reflected ALIGNED and identified one SHA handoff gap. Main folded +it into 020: candidate C must equal the regression-approved SHA; intervening product +changes require affected regression and matrix review. This preserves the accepted flow. diff --git a/devlog/_plan/261003_release_stabilization/010_stabilization.md b/devlog/_plan/261003_release_stabilization/010_stabilization.md new file mode 100644 index 00000000000..55437c5d02e --- /dev/null +++ b/devlog/_plan/261003_release_stabilization/010_stabilization.md @@ -0,0 +1,71 @@ +# Scoped stabilization and progressive integration + +Depends on the roadmap. The change is from open/unverified source PRs to reviewed, +tested PRs landed on dev with traceable authorship and issue dispositions. + +## Dispatch change map + +NEW ignored `.tmp/release-stabilization/lanes.json`: per lane id, canonical task/host, +worktree, branch, base SHA, scope, PR/head, status and evidence. Creation receipts +retain provisional IDs separately until host evidence establishes canonical identity. +MODIFY this unit with source PR dispositions and integration proof as each lands. +No runtime edits are authorized by the roadmap cycle itself. + +Five ordinary worktree tasks start on freshly fetched dev, use inherited model settings, +and may commit, push their own branches, create/revise PRs and run relevant CI. Only the +coordinator merges, closes superseded sources, promotes or publishes. Existing PRs are +preferred when writable and coherent; carries preserve Co-authored-by trailers. + +Creation during the coordinator roadmap is planning-only: P/A/docs are allowed, +but runtime edits, pushes and CI dispatch wait for the coordinator's explicit +`ROADMAP LOCKED` message after the roadmap D receipt. Record its commit/receipt +and per-lane delivery result in the manifest. Pending creation is not permission +to recreate a lane; verify the canonical task identity before delivering the gate. + +| Lane | Scope | Primary runtime owners | Required activated evidence | +| --- | --- | --- | --- | +| A | #6504, #6508 | `src/server/responses/fetch-helpers.ts`, Messages/Responses error translation | oversized input yields actionable non-retrying error; large native UTF-8 upload preserves bytes and completes; small/non-native paths unchanged | +| B | #6496, #6507, #6505 | `src/codex/auth-api/`, `src/codex/main-account.ts`, account runtime/store, `src/combos/failover.ts`, Responses auth/combo files | revoked credential refresh/refusal; caller-owned bearer separation; plan-ineligible target hops while unrelated 400 remains terminal | +| C | #6502/#6497, #6509 | `src/providers/antigravity-models.ts`, `src/usage/expected-prices.ts`, `src/adapters/ollama-native.ts` | actual discovered wire IDs/effort routes including saved selection; live 404 comparison if accessible; tool batch/commentary/results replay with orphan and duplicate negatives | +| D | #6076 | `src/cli/gui.ts`, `src/lib/gui-pair-capability.ts`, `src/server/gui-session.ts`, management link routes/registry | independent security review; legitimate enrollment and join remain usable; unpaired attempts rejected; real bounded enrollment/join/restart acceptance | +| E | #6370, #6378 | spend ledger/continuity/budget, request accounting, `src/oauth/anthropic-routing.ts`, `src/oauth/index.ts`, token detection/store | historical upgrade admission and budget conservation; unresolved/corrupt history preserves guard; account rotation cannot adopt unrelated identity | + +Each lane owns adjacent tests and its structure/user docs. Test inventory edits are +additive and the coordinator reconciles both inventories. B owns auth changes in +`core-options.ts`/`core-combo.ts`; E owns spending changes and rebases after B before +claiming merged-tree proof. A must coordinate any auth/combo overlap before writing. +A also lands before E's final composition checks; C and D prepare independently. +No concurrent checkout has authority over another lane's branch. + +## Integration procedure + +1. Refresh live dev, source PR head/base, membership and current reviews. Capture exact + diff/commit provenance in scratch; do not replay stale patches unreviewed. +2. Each lane plans and audits its exact changes, performs activated focused tests, + typecheck, privacy/structure and applicable GUI/docs checks, then independent review. + Findings are fixed or rebutted against source evidence, never cleared by fiat. +3. PR readiness requires successful exact-head required CI with actual expected tests; + absent, skipped, cancelled or old-head tests do not pass. Record security approval + for auth/pairing/accounting boundaries. No lane runs final lane=all CI. +4. Coordinator verifies the returned diff, attribution, live head/base and review state, + records authorized maintainer integration, and merges one PR at a time. A failed + validation prevents the merge command. Refresh the next lane against landed dev. +5. Resolve post-merge review threads, close actually superseded source PRs with carry + links, and close resolved issues with truthful acceptance limits. + +Coordinator additionally owns #6220 launcher replay and #6473 Windows autostart acceptance. +Use available isolated environments, preserve production state and redact observations. +An absent launcher/account/Windows session is an explicit unmet native acceptance row, +not a passing fixture test. Classify its actual release consequence before candidate GO. + +Security-sensitive findings or draft repair reasoning remain in ignored scratch. +Public plan text records only already-public scopes. A source PR with unresolved +adoption defects may be excluded only with an explicit evidence-backed disposition; +an unresolved existing release blocker cannot be hidden by excluding its proposed fix. + +## Completion evidence + +For every named item: current state, source and landed SHA or precise exclusion, +test commands/results, CI event/run/attempt/SHA, review/security disposition and +remaining native limits. Preserve failures. Do not run five full local suites in +parallel; focused local coverage plus required hosted coverage is the declared strategy. diff --git a/devlog/_plan/261003_release_stabilization/011_native_and_merge_acceptance.md b/devlog/_plan/261003_release_stabilization/011_native_and_merge_acceptance.md new file mode 100644 index 00000000000..ce7d29534e4 --- /dev/null +++ b/devlog/_plan/261003_release_stabilization/011_native_and_merge_acceptance.md @@ -0,0 +1,72 @@ +# Coordinator stabilization acceptance + +Consumes 010 after roadmap D at commit 2152fdfeb8. The prior conclusion was a +locked five-lane plan with a separate final regression phase, not runtime success. +Architect decisions S-01 through S-06 below refine that same direction. + +## S-01: native acceptance receipts + +NEW ignored `launcher-acceptance.json` and `windows-acceptance.json` under +`.tmp/release-stabilization/`: source/artifact SHA, prerequisites, invoked synthetic +scenario, expected/observed result, cleanup and verification boundary. + +For #6220: + +1. Read-only discovery establishes whether the actual launcher is installed, reachable + and already authenticated. Do not install a browser extension or retrieve cookies. +2. If available, use a fresh isolated CLI home/session and the same synthetic prompt + against the launcher directly and through an isolated proxy at the candidate SHA. + Both must complete the same minimal current-turn request. Then exercise one bounded + synthetic client-owned tool result and its follow-up through both routes. +3. Enable only the carried per-provider compatibility opt-ins in the isolated proxy + config; preserve ordinary-provider stripping and xAI ID repair in focused tests. +4. Capture structural/error metadata only, never cookies, tokens or private histories. + Stop only owned processes and remove only owned temporary state. If the required + launcher/account is missing, record NOT EXECUTED with prerequisites and rationale; + fixtures cannot certify this acceptance. + +For #6473: + +1. Read-only discovery checks available Windows host/toolchain and installed artifact + identity. Do not toggle the operator's existing Start at Login setting or overwrite + its Run entry as an incidental test. +2. If the patched packaged app can be exercised in an isolated Windows user/session, + verify first registration quotes the executable under a path containing spaces; + seed the legacy unquoted entry in that isolated profile, run the repair path, and + read back the quoted command with arguments intact. Exercise login startup there. +3. If only native test execution is available, run a source-derived bounded probe with + a unique temporary registry value and owned executable path; verify registration, + repair and cleanup through actual Windows APIs. Report this as native component + proof, not packaged-installer or login acceptance. +4. No unavailable package/session is treated as tested. Final release disposition + distinguishes the report's observed registration defect from an unconfirmed startup + failure and states precisely which behavior the collected evidence covers. + +## S-02 through S-04: merge handoff + +S-02 preserves A/B-before-E final composition and permits C/D in independent ready slots. +S-03 adds per-head merge receipts: source PR/head, carry authors, current base, test +coverage, exact-head CI event/run/attempt, independent review/security verdict and limits. +S-04 uses `scripts/ci/assert-mergeable-review.sh --maintainer-integration` for dev only. +The helper is actor/review evidence, not CI/security proof. A failed command stops the +dependent merge. Immediately re-read live head, dev target, actor and objections before +head-pinned merge, then verify the actual merge commit and late review threads. + +The helper has been read from source and its command shape verified. It is intentionally +not run against an arbitrary PR during planning: no lane candidate exists yet. Each +candidate's actual invocation is recorded before its own merge; no passing claim exists now. + +## S-05 and S-06: phase handoff + +S-05 sends completed stabilization to 015 independent parallel regression; native +receipts and lane reviews do not replace that phase. S-06 keeps publication outside +this work phase, preserves main approval/push-CI policy and retains actual native gaps +for the final release-impact decision. A goalplan label never grants bypass authority. + +## Consultation + +Architect (receipt retained in private coordination evidence) proposed these six decisions. +Main accepted them. The same architect reflected ALIGNED on 011/010/015 with no +material gaps. Independent reviewer (receipt retained in private coordination evidence) +checked 010/011/015 and the policy boundary: VERDICT PASS, no blockers. This is +plan acceptance only; runtime/native and merge evidence is still required. diff --git a/devlog/_plan/261003_release_stabilization/012_integration_evidence.md b/devlog/_plan/261003_release_stabilization/012_integration_evidence.md new file mode 100644 index 00000000000..9151fe76aac --- /dev/null +++ b/devlog/_plan/261003_release_stabilization/012_integration_evidence.md @@ -0,0 +1,128 @@ +# Progressive integration evidence + +This records landed work and remaining gates, not a production release receipt. + +## Landed scope ledger + +| PR | Landed dev commit | Final PR CI | Disposition | +| --- | --- | --- | --- | +| #6513 | `e77bfb4901d405724edc4295caf5d9f0675a672e` | 37130246238 | #6508 fully superseded and closed | +| #6519 | `a141b83623a3f1677f23477e91b9a42a74f495f7` | 37131234397 | #6509 fully superseded and closed | +| #6516 | `36330ae2ef120daa75566280f9c0b1e51eb2a36b` | 37132320824 | #6504 proxy-error bug closed with explicit upstream/client limits | +| #6514 | `aa40fb4158260196669346ab0e0dd55f679fd13e` | 37132498856 | #6497 partially carried; source remains open | +| #6515 | `e601cefcebcc20b2e04a04821ab45df6538d98f9` | 37132811309 | #6496 fully superseded and closed | +| #6518 | `358b8ffd4c7f95237dadee1e9a5b4fedb671f4f4` | 37135487096 | #6378 superseded and closed after identity/cleanup review closure | +| #6517 | `a99de42e7a5986c2443805fa6d833b8152f967ff` | 37135315210 | #6076 subsequently marked MERGED by preserved ancestry | +| #6523 | `a41f67273c4937b852f26d726001641fe2026f63` | 37145111209 | #6507 account behavior carried; source superseded and closed, diagnostic-text logging excluded | +| #6527 | `ab68539036fa89e0734ea21aa6b525ae4cc3ae36` | 37146395644 | #6505 superseded and closed; nested-only HTTP follow-up pending | +| #6538 | `efc20e700b6fce67578e07b94336fc71aebc5a1c` | 37148359842 | Existing-format capacity guard; canonical #6370 remains excluded | + +All listed runs are successful final-head pull_request runs. Their actual checkout +logs, selected executed jobs, actor/base/head gates and post-merge review counts +were inspected. Later integrations also compare the computed union with the actual +merged parent/tree and check repository line caps plus test-map parity. Full +cross-platform candidate coverage is still a separate unmet gate. + +Canonical spend integration remains open. +The identity cleanup findings were repaired and closed at the exact merged head; +pairing's original technical objection was addressed with actual CLI/HTTP/native +evidence, without dismissing another maintainer's formal historical review. The +optional CodeRabbit queue was pending on #6517 and explicitly not counted as a +passing review; all published findings were resolved and independent review passed. +Final late-review checks remain required. A local broad import-graph run with failures is preserved as +failed diagnostic evidence, never relabeled as passing by these PR results. + +The stored-main carry includes alternate-resolution cancellation, read-fence and +late pinned-selector/diagnostic/single-snapshot repairs. A final additive hard-lock +spy changed the head; the head-pinned guard stopped the attempted old-head merge, +and the new head received its own CI and narrow independent closure before landing. + +## Large native HTTP uploads + +PR #6513 merged into dev as `e77bfb4901d405724edc4295caf5d9f0675a672e`. +Reviewed head: `db094c49c7cce13500b1f1f63678f174261fc9af`. +Required scoped CI: run 37130246238, attempt 1, pull_request event, success. +The actual checkout log binds that head to base `9f89b7265b754eb681215ad327fc9459af37b9e1`. +All four Linux shards and expected scoped auxiliary jobs ran successfully; full +macOS/Windows suites were skipped by scope and remain for the final candidate. + +The coordinator reviewed the final-send conversion, destination/egress/header/abort +boundaries and no-replay behavior, inspected local 199-pass/one-runtime-skip evidence, +and verified the six-failure mutation result. The documentation threshold finding +was fixed; zero review threads remained unresolved after merge. The maintainer +integration/security decision is recorded on the PR. + +Source #6508 remained at the fully carried head `dd4fc9a8f732699d88f46058c3298073d9aa2317`. +It was commented, labeled superseded and closed after landing, with source author +credit retained. Child #6516 must consume the actual landed dev base before merge. + +## Shared registry repair + +B's separate commit `29a9e91f400d24ac746a18dfe3af357f5da6dec3` removes 31 identical +duplicate entries from each test-layout registry. The coordinator independently +verified duplicate-value equality and unchanged parsed mappings. C/D may adopt that +same repair while retaining their unique registrations. This does not raise a cap +or remove a test; hosted union checks still govern each candidate. + +## Native acceptance limits + +The Windows production command helper compiled and ran on Windows 11: three tests +passed. A separate native CommandLineToArgvW probe passed four quoted-path cases +and the expected failure of an unquoted negative control. The source hash is +`6c8e206b36e8330c6fd1888960d798144de29319d7debb8cfbb567173117c583`. +These results do not establish packaged registration/migration or login startup. + +A focused source review falsified the suspected Task Manager override regression: +the pinned auto-launch 0.5.0 status implementation checks StartupApproved before +the migration writer is called. No code change follows that disproven hypothesis. +Native disabled-registry behavior remains untested. + +No running real launcher was available for #6220's direct/routed tool-followup +acceptance. It remains NOT EXECUTED. Neither this fact nor an administratively +closed issue is converted into passing runtime evidence. + +## Model-refusal carry and combined boundary check + +PR #6527 landed at corrected head `af350a1924ee55ddc60fe9fa04289d1aa494f437` +with independent family-level review and explicit security review. Its applicable +CI ran against current dev `a41f67273c4937b852f26d726001641fe2026f63`; the merged +union tree matched the reviewed head tree. Source #6505 was closed as superseded +after the carry landed, with attribution preserved. + +The original late-review correction was insufficient for mixed root/response +envelopes. The replacement applies a shared field-presence rule, with null and +nonmatching values covered. Local evidence records four failures before repair, +79 passes after repair, and 374 broader focused passes. The HTTP400 constraint +and existing SSE status reconstruction remain explicit limits. + +After integrating the nine implementation carries into the coordinator checkout, +`bun run test` with eight explicit files passed 281 tests / 1422 assertions. The +files cover native upload boundaries, Claude outbound and Messages errors, Ollama +replay, combo plan/model refusal, stored-main refresh, alternate cancellation and +grant refusal. The command and full output are retained in the ignored integration +receipt. This is a combined boundary check, not the separate final independent +parallel regression or complete platform CI; both remain pending. + +## Inclusion decision and outstanding repairs + +The canonical spend migration proposed for #6370 is excluded from this release. +Upgrade and recovery compatibility acceptance did not pass; the original PR stays +open and its implementation work remains preserved in the owning worktree. This +decision does not declare existing spend behavior correct. The independent existing-format capacity guard in PR #6538 landed after review +and successful exact-head CI37148359842. Its ledger format and hashing are unchanged. +Private investigation and review evidence remain in ignored scratch. + +A post-merge review of #6527 identified a nested-only HTTP error-carrier case that +still stops fallback. It is assigned to follow-up PR #6540 with independent +closure and fresh CI; final regression cannot start from the current known-defective +head. Lane C fixture repairs also remain pending. Final independent regression has +not run; it starts after pending repairs land and their required checks pass. PR #6536 +(`dd9a980ec071285e628a35d8cdefaea785b8916b`) separately archives Lane B's public +record, with corrected checkpoint counts and successful docs-only CI37147122129. +Its skipped runtime jobs are not counted as runtime verification. + +The #6538 review records a nonblocking diagnostic limitation: native handlers can +report a generic spend-exhausted reason when capacity prevented booking. The send +is refused correctly. Supplied focused evidence totals 213 passes, and the original +capacity probe changes from failure before repair to success after it. Final +combined acceptance remains pending the other scoped repairs. diff --git a/devlog/_plan/261003_release_stabilization/015_independent_regression.md b/devlog/_plan/261003_release_stabilization/015_independent_regression.md new file mode 100644 index 00000000000..c87a2b14172 --- /dev/null +++ b/devlog/_plan/261003_release_stabilization/015_independent_regression.md @@ -0,0 +1,38 @@ +# Independent adversarial regression before release + +Depends on scoped stabilization. This separate PABCD work phase implements the user's +additional requirement: parallel inherited subagents stress the integrated candidate, +then findings are repaired and rechecked before final CI and publication. + +NEW ignored `.tmp/release-stabilization/regression-matrix.json`: candidate SHA, +per-surface test commands, activated scenarios, child identity, outcomes, diagnostics, +cleanup receipts, findings and repair/retest links. MODIFY the release unit only with +sanitized outcomes. Runtime repairs require a bounded audited patch plan and PR; never +edit a frozen candidate under still-running verification workers. + +At P, pin integrated dev and prepare independent packets grounded in the actual diff. +At A, audit scenario reachability and coverage gaps. At B, dispatch fresh-context leaf +subagents with inherited settings and disjoint testing/process/state ownership: + +1. Native credential/account/combo regression: revoked grants, transient failures, + true caller credentials, replacement tokens, ineligible models and unrelated errors. +2. Requests/compaction/tools: oversized input and recovery, multi-byte large upload, + cancellation/replay refusal, partial tool batches, commentary, duplicate/orphan IDs. +3. Pairing/accounting/upgrade: legitimate enrollment and negative auth, one-time/expired + capabilities, old ledgers and ambiguous history, budget conservation, credential + identity rotation. Unpublished security diagnostics remain in ignored scratch. +4. Provider/platform integration: Antigravity discovered/saved effort paths, launcher + replay, Windows startup registration/repair and available packaged smoke evidence. + +Leaves must not run git mutations, spawn, mutate goals/FSM, change user account state, +or share OPENCODEX_HOME/temp directories. Use distinct owned fixtures and bounded +process trees; obey shared test locks and do not remove another worker's lock. +Parallel independent analysis/test preparation is required; resource-heavy commands +may be scheduled to avoid invalidating each other's evidence. No five full suites. + +C verifies every returned command/result and reproduces actionable findings. Repairs +land through reviewed PRs and trigger affected regression rechecks at the new SHA; +unrelated passing commands are not repeated without a reason. A fresh independent +reviewer audits the complete matrix and residual risks. D closes only with findings +resolved or explicitly disproven, reachable negative-path evidence, honest native +coverage limits and a concrete GO/NO-GO for final candidate CI. diff --git a/devlog/_plan/261003_release_stabilization/020_candidate.md b/devlog/_plan/261003_release_stabilization/020_candidate.md new file mode 100644 index 00000000000..32665c03d40 --- /dev/null +++ b/devlog/_plan/261003_release_stabilization/020_candidate.md @@ -0,0 +1,32 @@ +# Final candidate verification + +Depends on independent regression closure. NEW ignored candidate receipt JSON records the immutable +SHA, intended version, merged PR ledger, runtime delta and workflow observations. +MODIFY this unit with the GO/NO-GO decision; no unplanned product change belongs here. + +1. Fetch dev, verify every scoped disposition and review thread after merge, and pin C. + Inspect concurrent landed changes rather than silently adding an unreviewed release. + C must match the regression-approved SHA. Any intervening product delta returns + through affected regression checks and independent matrix review before candidate GO. +2. Run `gh workflow run ci.yml -R lidge-jun/opencodex --ref dev -f lane=all` only once + the integration set is settled. Verify the created run's head SHA equals C. If the + ref raced, preserve the receipt and obtain valid evidence for the actual candidate. +3. Observe run/attempt and jobs with bounded waits. Require Linux/macOS/Windows test + shards, applicable package/keyring/helper jobs, docs/GUI/structure/privacy and + desktop build proof. Record all skips distinctly. Use repository's existing + `.tmp/train-recovery/verify-final-ci.py` only after inspecting its assumptions; + otherwise inspect raw job and step data directly. The previous run is not proof. +4. Failure means a named repair task/cycle, regression evidence and rerun of affected + coverage plus candidate consistency. Never increase caps or weaken assertions just + to obtain green. Preserve the failed run and exact triggering command. +5. C is the code/version snapshot to promote. Later dev pre-move does not change C. + Review the candidate-to-promotion delta and require only intentional version or + history changes; additional product changes invalidate the candidate proof. + +Existing #4956 is a CI-completion risk: an incomplete/hung required job blocks GO. +An issue remaining open alone is not a failed candidate. #6220/#6473 retain explicit +native evidence limits even if their administrative issue states are closed. + +The final independent gate reads candidate SHA, coverage receipts, security dispositions, +remaining native limitations and source PR lineage. No GO with unresolved release blockers. +This full candidate run does not replace mandatory push-event CI on the main promotion SHA. diff --git a/devlog/_plan/261003_release_stabilization/030_publication.md b/devlog/_plan/261003_release_stabilization/030_publication.md new file mode 100644 index 00000000000..06f70d72d1a --- /dev/null +++ b/devlog/_plan/261003_release_stabilization/030_publication.md @@ -0,0 +1,58 @@ +# Production promotion and publication + +Depends on the accepted immutable candidate C. Intended next stable is provisionally +2.77.0, subject to fresh registry/tag ordering and concurrent release inspection. +No preview release is requested. Existing OIDC release workflow is the publication path. + +## Version and branch changes + +MODIFY exactly the four version sources through their owner command/workflow when needed: +`package.json`, `desktop/src-tauri/tauri.conf.json`, `desktop/src-tauri/Cargo.toml`, +`desktop/src-tauri/Cargo.lock`. Before: dev version equals target V. After: dev strictly +outranks V; candidate C and main promotion retain V. Do not manually rewrite lock content. + +1. Confirm V is unused on npm, Git tags and GitHub releases and outranks global release + tags under `scripts/version-line.ts`. Read live `MAINTAINERS.md` and rulesets. +2. Dispatch `dev-version-bump.yml` from main with `intended-version=V`, `mode=pre-move`. + Review/merge its dev PR after applicable CI. If dev already outranks V, retain its + explicit no-op result. Do not promote the advanced dev version by accident. +3. Prepare `codex/promote-main-V` from C. Reconcile main ancestry with documented + promotion tooling while preserving the candidate tree. A history-only merge using + the ours strategy is valid only after proving every main-only code delta is already + present or deliberately reconciled; otherwise merge normally and revalidate. +4. Open a main-targeted promotion PR using the repository template and required GUI + screenshot evidence from landed PR assets. Observe actual main review/ruleset policy; + the dev-only self-integration exception does not authorize bypassing main approval. + The owner's explicit release authorization for this unit is a separate promotion + authority: current policy says release review by both maintainers is recommended + when practical, not unconditionally required for a maintainer-authored promotion. + Use the existing admin PR-only route only after independent review/CI and current + actor entitlement are verified; record that owner authorization on the promotion PR. + This follows the documented owner-authorized promotion procedure and does not alter + rulesets, waive outstanding objections, or permit a direct main push. +5. After authorized promotion, capture exact main SHA R and its successful **push-event** + Cross-platform CI and required Service lifecycle run. C's manual run is not a substitute. +6. Verify R still owns main immediately before dispatching the supported release workflow: + `gh workflow run release.yml -R lidge-jun/opencodex --ref main -f version=V -f tag=latest + -f dry-run=false -f expected-sha=R`. This is the same guarded publication workflow + used by `scripts/release.ts`; no local npm publish or direct protected-branch push. + +## Publication proof and recovery + +Observe preflight, platform packaging, npm publication, GitHub asset attachment and +deployed docs. NEW ignored publication receipt records builder/run/attempt, exact source, +registry version/dist-tag/gitHead/integrity/provenance, release tag and all expected assets, +checksums, signed updater metadata and isolated install/launch smoke results. +Compare expected artifacts with the workflow and previous stable release, not a fixed count. +Do not upgrade the operator's installed proxy as an incidental smoke test. + +If npm publication is acknowledged but a later job fails, retain that evidence and use +the explicit `resume-after-npm-publish=true` path only when official registry gitHead +matches R. Never republish an existing version or force-move a public tag. +Recovery keeps the prior stable package/artifacts available and does not downgrade +user data; if a bad new version requires withdrawal or a tag rollback, stop that mutation +for its separate concrete decision and prepare a forward fix within scope. + +Completion: publication workflow and deployed surfaces verified, exact artifact proof +recorded, remaining platform limits stated, and this unit moved to `_fin/` only after +its public outcome exists. Release notes retain source-author attribution. diff --git a/devlog/_plan/261003_train_recovery/000_plan.md b/devlog/_plan/261003_train_recovery/000_plan.md new file mode 100644 index 00000000000..328d396f927 --- /dev/null +++ b/devlog/_plan/261003_train_recovery/000_plan.md @@ -0,0 +1,97 @@ +# Recover the integration train and prepare the production candidate + +The previous integration stopped after combining 31 contributor changes and before +integrating two completed issue implementations. Preserve those commits, review the +combined behavior in five isolated worktree lanes, repair demonstrated defects, and +land reviewed integration candidates on dev. The final platform run establishes +production readiness; this unit does not publish a release. + +Reader: the integrating maintainer, deciding what can land and what remains unverified. + +## Scope and completion contract + +- Archetype: satisfy the recovered integration specification (C4 for sensitive surfaces). +- Trigger: continuation of the interrupted train with five worktree chats and inherited subagents. +- Goal: reviewed dev integration, attributable commits, final cross-platform evidence. +- Non-goals: release/promotion/deployment/install, credential changes, unrelated backlog, + revival of policy-blocked items, native GitHub stack registration. +- Verifier: diff/ancestry and attribution inspection; focused explicit regression files; + one final root/GUI typecheck as relevant; existing structure/layout/privacy gates; + required final PR checks and final dev workflow_dispatch CI lane=all. +- Stop: only after the disposition ledger and exact-SHA CI evidence support readiness; + absent/skipped/cancelled checks stay unverified. External acceptance limits stay explicit. +- Memory artifact: this numbered unit; private drafts and raw session evidence stay in scratch. +- Outcomes: DONE with evidence, NOOP for proven already-landed items, or a named unresolved + blocker. No arbitrary time/token bound was supplied and none is invented. +- Escalation: unresolved product policy, external access, or new out-of-scope authority. + The user authorized branch preparation, PR integration and coordinator merge judgment. +- Resources: existing local git/gh identity, five lane tasks, bounded inherited subagents; + no full local suites, test:changed, prepush, dependency install or native builds. + CI dispatch only once the candidate is assembled; necessary failure repair remains in scope. + +## Dependency-ordered work phases + +1. Roadmap: lock source commits, lane ownership and verification/closure boundaries. +2. Integration: consume existing patches, review/repair each lane, reconcile the union, + publish ready A-D first, then E, and land eligible work through the maintainer integration policy. +3. Verification: run full cross-platform CI on final dev and prepare the readiness handoff. + +Detailed executable operations are in 010_integration.md and 020_verification.md. +Architecture/source-of-truth remains structure/INDEX.md and its owned documents; each +runtime repair must update its owner doc in the same change. + +## Baseline and exclusions + +Local train: da40c734a558ea32ce391613f5a0309d14cfce81. Remote dev observed: +2e3acab46e (TokenLab #6474); Grok #6482 is also already landed. The existing train +contains a historical #6474 carry; preserve history but introduce no duplicate content. + +Accepted source sets (recovered lane-a.md through lane-d.md packets): A #6474 #6463 #6445 #6470 #6417 #6254 #6382 #6416 #6461; +B #6450 #6449 #6451 #6452 #6453 #6192 #6466; +C #6459 #6479 #6119 #6455 #6477 #6457 #6460 #6415 #6335; +D #6426 #6471 #6149 #6151 #6405 #6458. +Issue implementations #6313 and #6223 are explicitly included. + +Deferred dispositions: #6378 #6143 #6472 #6370 #6076 #6301 #6381 #6436 #6336; +#5253 is superseded by the selected locale contribution. Research-only #2511 #4961 +#4198 stay deferred for unresolved policy/access. Preserve existing rationale; do not +mistake an effort estimate for a product rejection. + +Do not close #6220 without real launcher acceptance or #6473 without native Windows +acceptance. Passing source tests alone is insufficient. Other issue closures require +fresh acceptance review, not just a commit message. + +## Consultation and evidence + +Recovery explorer established the source scope and original cancellation; main checked +the original user request and follow-up dispatch. Existing lane reports and logs are +historical evidence, not current verification. The architect proposal, main decisions, +reflection, independent audit and current lane manifest are recorded in scratch and +summarized here when received. No private transcript is copied into public git. + +Architect consultation: REC-ARCH-01/04/06/07 accepted; REC-ARCH-02/03/05 amended +to keep original A-D carry ownership and put both complete issue slices in E. +REC-ARCH-08 amended to final dev dispatch after merge, preserving pre-merge PR CI. +Same architect returned ALIGNED on the three documents; its two verification gaps +were folded in: check returned headSha and require fresh comprehensive evidence after +a repaired candidate changes SHA. Independent audit remains a separate gate. + +Roadmap audit: independent reviewer PASS, no blockers. Fresh reader understood the +answer (preserve and review the recovered train), evidence (pinned git objects and +workflow/policy contracts), and next action (bounded implementation handoff). No +reader-structure repair remained. Document checker passed on numbered documents, +source object existence/parentage, workflow inputs and diff whitespace. + +## Integration cycle entry + +Previous D: roadmap 995dee54a0 locked after semantic audit and document receipt. +Direction remains to integrate five lane handoffs. The executable 010 design is +unchanged. Fresh preflight confirms the contributor train is clean, original work +remains preserved, and current dev is 2e3acab46e. Five lane tasks now own bounded +implementation/verification, with production code changes limited to demonstrated +defects and the two authorized issue implementations. Root/GUI dependency manifests +and lockfiles match an available existing dependency tree; no install is required. + +User steering: land ready work progressively. A-D is ready for its PR now; E follows +in a separate PR. Do not wait for E to publish or land A-D. Final manual lane=all +remains after both landings. diff --git a/devlog/_plan/261003_train_recovery/010_integration.md b/devlog/_plan/261003_train_recovery/010_integration.md new file mode 100644 index 00000000000..84b3572286a --- /dev/null +++ b/devlog/_plan/261003_train_recovery/010_integration.md @@ -0,0 +1,69 @@ +# Assemble and review the existing patches + +Depends on: roadmap closure. Existing production code is not recreated during roadmap. + +## Exact baseline delta + +In the coordinator checkout on codex/recover-train-261003, preserve current docs commit, +then merge da40c734a558ea32ce391613f5a0309d14cfce81, then origin/dev after a fresh fetch. +Inspect each conflict with both source hunks and the owning invariant; never concatenate +JSON or prose mechanically. Preserve the two existing union fixes 2a3652c4da and +da40c734a5. Exact inherited file deltas are the existing git objects, inspected with +`git diff 762501439442fa00ec950802e9285f72af949167...da40c734a558ea32ce391613f5a0309d14cfce81`. +No deletion or rewrite of original worktrees/branches is needed. + +Lane E adds the exact existing issue deltas from 8e2152fa30913955b6640b61b14f6bc522eb3301 +and 3fec4a4e312cefe7fe5e11f313e1b6bdefc66148, based on da40c734a5. Their authoritative +before/after patches are `git show `. The first changes Claude model-force config, +launch env, management routes, CLI, Subagents UI/tests/docs. The second changes RemoteLink +UI, all locale catalogs, its focused tests, docs and structure/remote-link.md. +These are modifications/new files exactly enumerated by `git diff-tree --no-commit-id +--name-status -r `; no speculative implementation outside those deltas is prescribed. +New defect repairs require a concrete lane plan with paths and activation evidence first. + +## Five lane ownerships + +- A: original A Responses/SSE/provider/header/log contracts and adjacent tests/docs. +- B: original B account/credit/trust/buffer boundaries and negative tests/docs. +- C: original C platform/CLI/service/shim/desktop zoom and adjacent tests/docs. +- D: original D GUI/usage/compaction/client exports/locale and adjacent tests/docs. +- E: #6313 and #6223 complete implementation, their tests and interface/doc updates. + +Each task starts from the same train snapshot in its own managed worktree and uses +inherited subagents for bounded investigation/review. Preliminary dispatch is read-only. +Only after the roadmap closes does the coordinator grant implementation. Each lane +creates its own codex/recover-train-261003- branch and returns commits; no lane +push/PR/merge/CI permission. Shared locale/layout/docs conflicts are coordinator-owned +at integration time. E resolves conflicts between its two issue patches; main resolves +conflicts between lanes. No concurrent writer touches the coordinator branch. + +## Activation and review acceptance + +A: forced-answer tool declarations, CR/LF framing, opt-in metadata/header behavior and +ordinary-path stripping; no credential headers. B: default-off credit spending, scoped +provider access, oversized catalog/image/stream bounds and trust rejection. C: quoting +and stopped-port detection, nonblocking mirror reads, stable fnm launch, zoom persistence. +D: rapid visibility toggles, weighted throughput, source-scoped compaction, editor override +preservation and exhaustive locale coverage. E: unset force preserves launch behavior; +set force reaches runtime/CLI/API/UI; invalid model rejected; RemoteLink failed/empty/retry, +manual entry, keyboard access and fingerprint confirmation remain distinct and usable. + +Run only explicit focused test files in bounded sequential batches (GUI --isolate). +New tests register both layout maps. Reuse available dependency trees without installing. +Missing dependencies are missing evidence. Coordinate typechecks to avoid resource contention. +Rendered UI evidence must be observed; keep screenshots off the PR branch and publish +through the existing pr-assets mechanism if needed for the final PR description. + +## Final integration operation + +Inspect lane diffs and negative-path evidence, fix union conflicts and generated maps, +check co-author trailers and docs ownership, record explicit security review. Preserve +required CI and existing maintainer objections. User steering now requires rolling landing: publish the verified A-D candidate first, +then E as a separate follow-up PR. Open each ready PR targeting dev with all template sections, accurate verification limits and +GUI evidence. Attach it to this chat. Revalidate live actor, base, head, reviews, membership +and check evidence before coordinator-authorized maintainer integration. Do not bypass +failed required checks. Use an ordinary PR, not native stack operations. + +User delivery correction: do not hold verified A-D behind E. Required checks gate each +PR; the full manual cross-platform run remains after the last dev landing. This changes +delivery order, not scope or verification requirements. diff --git a/devlog/_plan/261003_train_recovery/020_verification.md b/devlog/_plan/261003_train_recovery/020_verification.md new file mode 100644 index 00000000000..48939396d80 --- /dev/null +++ b/devlog/_plan/261003_train_recovery/020_verification.md @@ -0,0 +1,30 @@ +# Verify final dev and record production readiness + +Depends on: reviewed integration landing. No production code change is planned here; +any failing behavior returns to a concrete repair plan and receives fresh evidence. + +1. Read .github/workflows/ci.yml at final dev, enumerate expected jobs and supported + workflow_dispatch inputs. Confirm the selected reference resolves to the landed SHA. +2. Dispatch the existing Cross-platform CI with lane=all on dev, after all train changes + have landed. Preserve dispatch run ID/event/head SHA/attempt and every expected job. + Require the returned run headSha to equal the intended candidate. At completion, + verify dev still equals it or limit readiness explicitly to the tested SHA. +3. Watch one aggregate observation at a time with intervals appropriate for hosted jobs; + avoid parallel polling by lanes. Pending, skipped, cancelled, approval-blocked and + failed have distinct recorded outcomes. Do not call a zero-failure empty set passing. +4. Diagnose any failures from the actual job logs, distinguish baseline/environment from + changed-code failures, and repair authorized regressions through PRs. Rerun only the + failed/missing jobs only for the same SHA. A repair that changes the candidate needs + a fresh comprehensive run; do not combine passing jobs from different SHAs. +5. Record final dev SHA, merged PR, original carry/issue dispositions, security review, + executed Linux/macOS/Windows coverage, GUI evidence and residual live/native limits. + No release, npm publish, main promotion, install, or deployment is part of this unit. + +MODIFY 000_plan.md: append evidence-backed terminal summary and remaining acceptance limits. +NEW 090_outcome.md: concise reader handoff with exact commit/run links and deferred items. +Move this unit to devlog/_fin only after a real terminal outcome is recorded. Public records +contain shipped outcomes, never private vulnerability drafts or raw personal session data. + +Proposed CI command is subject to the workflow-source check before execution: +`gh workflow run ci.yml -R lidge-jun/opencodex --ref dev -f lane=all`. +This command has not run during roadmap and does not itself prove a job passed. diff --git a/devlog/_plan/261003_train_recovery/030_carry_ledger.md b/devlog/_plan/261003_train_recovery/030_carry_ledger.md new file mode 100644 index 00000000000..e60d829e65c --- /dev/null +++ b/devlog/_plan/261003_train_recovery/030_carry_ledger.md @@ -0,0 +1,69 @@ +# Recovered contributor changes + +The train preserves 31 original carry commits. #6474 already landed separately; +the original recovery scope therefore contained 30 remaining source-PR changes plus +the separately reviewed issue implementations. Three source PRs subsequently landed +independently, leaving 27 source changes for the A-D integration PR. Source PRs are carried or reimplemented, not claimed +to be merged by retaining the carry commit ancestry. + +| Source PR | Carry commit | Contributor | +| --- | --- | --- | +| #6474 | `11bbc17d9ed40ed27121a526dbe80039c2b951cd` | hedging8563 | +| #6463 | `f5572a0031471b933a4bafe0236fb509ab78ddfd` | lcxhh521 | +| #6459 | `763dda2117e6d1817f62e0c142d8a45c3b5c9c1a` | luvs01 | +| #6445 | `10975564724b93789493522154919f2f6d5e8e19` | xianhongtao | +| #6426 | `6d4e40443c451039f7d215743aea925852565c49` | andrew05060414 | +| #6479 | `b0b884b2c940ed80de3e47256bb2f878882679c1` | Yuxin-Qiao | +| #6119 | `1ad42a70aeb6448f91a8a73037042f37e2f27480` | kdm1jkm | +| #6455 | `9f5194c96dcc1a757467eefe75e16cee6f6f9380` | luvs01 | +| #6471 | `9d3e6e252a362dd01e756bb48b3af3a75225bf4d` | lcxhh521 | +| #6457 | `26755d9622a8531e242ff06ba649c39c94ab4d3a` | luvs01 | +| #6477 | `00c69c38fc43636a6cb7bcd159ccff13886afed4` | andrew05060414 | +| #6460 | `b2d27e35a263817bb0b115dac5a97ab7f9206f4a` | luvs01 | +| #6415 | `4ef04ff37b2513a4355730ee452537cda6dfb041` | andrew05060414 | +| #6335 | `c294e5811999551272cf2196301cfe8b68fcc325` | sungyongcho | +| #6470 | `a85a43755ae164a324afa7ebd05b9f6e612baa86` | lcxhh521 | +| #6417 | `ce0e7672c8b5a974b61c4f593c0ee0ce2b437dec` | arikon | +| #6254 | `60ed4c65b93e449e77b29dedf83f2cfdf3228b9c` | codingbooo | +| #6382 | `7ccc5929cce938a20f38a50533ff1d79a79dd502` | 2836048681 | +| #6416 | `2abd7341a3c761bba14f54c91128ce656ab99ed6` | xyjk0511 | +| #6461 | `2753ef9e43c445fa0843c6a50fe3bbb970344fdf` | luvs01 | +| #6450 | `ce87c68077216ad0275036315410d1a1eab9e0c0` | luvs01 | +| #6449 | `f5483034009781f5492c884b37bd39b48c38de6d` | luvs01 | +| #6451 | `1108236af701a0a5cac4d44ae99a2d20c9de2da2` | luvs01 | +| #6452 | `dbed9c7b9a9d9266f7358805dbe784acbe2f1ff6` | luvs01 | +| #6453 | `0358e72c8c8ec9a4708aff7401632454ff178a3e` | luvs01 | +| #6192 | `f2641871d0871563810b4e87a27047d670310320` | agentHits | +| #6466 | `6ef255fd067342214ffd0518d11c8ecbe0cc894d` | Hylouis233 | +| #6149 | `3e65384642dd6d02d558a425c1a78654cea24ce6` | yuanyuanlove | +| #6151 | `de828619f84659603f6a13740401049f28dd4dde` | shawn-kim-ai | +| #6405 | `56ea6d76e36370bc341ec1592a9a290a459942d4` | imranshaiedi-byte | +| #6458 | `5d7efbb749ffb72efa6696571b73a4a874a8c61d` | rriosfelipe | + +The original B/C carry messages contain credit text but lack a trailer separator. +A clean consolidated contributor block accompanies the recovery ledger commit and +will accompany the final merge message, preserving graph attribution without +rewriting the recovered history. + +Potential resolved issues after acceptance: #6425, #6309, #6464 and #6465. +The latter retains explicit per-account paid-credit opt-in. #6313 and #6223 require +their complete issue acceptance, not just the old commit close lines. #6220 remains +open pending real launcher acceptance; #6473 remains open pending native Windows +acceptance. #6406/#6290 are references only. #5253 contributes no carried content. + +Maintainer requests on #6416, #6192, #6119, #6149 and #6151 require explicit +evidence-backed dispositions. Current source-head review state is not proof of +a defect in the recovered carry, but it must not be silently discarded. + +## Progressive source landings + +Under the updated delivery direction, #6459 landed at ee2e15f86da111bfac527842aade9ad7780e15fa, +#6455 at 115fa0322cd938da846957e237d4b97b38527bf6, and #6457 at +9fb79230ba8f1df8b6af13bbb6067c359a4f9344. Each retained successful exact-head PR CI, +no unresolved review threads or maintainer objection, and a recorded owner-integration +decision. The reviewed runtime/test blobs matched the recovery carries. Source PR +changes now in dev are reconciled into the train without duplicating their content. + +The selected pt-BR contribution supersedes #5253. Although no file content from that +proposal was copied, the superseded author's trailer is included in final delivery +as required by the repository's attribution policy. diff --git a/devlog/_plan/261003_train_recovery_a/000_plan.md b/devlog/_plan/261003_train_recovery_a/000_plan.md new file mode 100644 index 00000000000..cfd21d7c4bb --- /dev/null +++ b/devlog/_plan/261003_train_recovery_a/000_plan.md @@ -0,0 +1,17 @@ +# Lane A recovery + +Repair two demonstrated edge cases in the carried train: split CRLF completion repair and a discarded web-search recovery request at the iteration ceiling. Align Portuguese decode-window wording with the existing generation metric. Preserve the working carries from `da40c734a558ea32ce391613f5a0309d14cfce81`. + +- Satisfy-spec, single bounded implementation cycle, authorized by the coordinator after roadmap `995dee54a0`. +- Scope: existing terminal-repair and web-search owners, adjacent regressions, owning contracts, one locale string. No new abstraction or configuration. +- Non-goals: pushes, PRs, merges, installs, CI dispatch, releases, live account/provider mutation, full suites, broad typechecks, builds. Coordinator owns final integration and cross-platform proof. +- Verifiers: explicit focused Bun test files, GUI `--isolate`, structure/layout/privacy checks and independent inherited review. Baseline two-file regression suite recorded privately before implementation. +- Stop: local commits with passing focused checks, independent findings addressed, and private report in `.tmp/train-recovery/`. Unavailable live/native proof remains explicit, not a success claim. +- Resource envelope: bounded sequential test batches, at most eight files each; no user-specified token/time budget. Escalate scope growth or blocked dependencies to the coordinator; do not weaken acceptance. +- Detailed change map and activation scenarios: `010_repairs.md`. Security working notes remain ignored scratch only. + +## Consultation + +The coordinator's audited roadmap assigns these existing surfaces to Lane A. Local architect consultation and independent review evidence are recorded in the private recovery report; this plan retains the bounded repair decisions and public verification scope. + +Architect `01a10042-39d1-7980-a06f-f60df848a3ee` proposed A1/A2/A3; main accepted all three without changing owners or contracts. The same architect reviewed `010_repairs.md` after those decisions were recorded and returned ALIGNED with no gaps. diff --git a/devlog/_plan/261003_train_recovery_a/010_repairs.md b/devlog/_plan/261003_train_recovery_a/010_repairs.md new file mode 100644 index 00000000000..e1f25b5481d --- /dev/null +++ b/devlog/_plan/261003_train_recovery_a/010_repairs.md @@ -0,0 +1,20 @@ +# Bounded repairs + +Depends on: coordinator roadmap `995dee54a0`, train `da40c734a5` and Lane A discovery. + +1. A1: modify `src/server/responses-terminal-repair.ts` to remember a consumed delimiter ending in CR only when its buffer is empty. Consume exactly one following LF as a delimiter continuation while forwarding its byte unchanged. Empty chunks retain pending state; any non-LF content settles it. Do not relax genuinely unframed suffix rejection or lifecycle eligibility. + Extend `tests/responses/responses-terminal-repair.test.ts`: complete terminal-less CRLF lifecycle split before final LF versus unsplit, repeated CRLF splits, empty intervening chunks, CR/CR and LF/CR delayed-LF delimiters, a second LF, bare CR, non-LF suffix, real terminal, and budget/timer release. Update `structure/transports/responses-wire-shapes.md`. +2. A2: modify `src/web-search/run-turn-loop.ts` to check the remaining iteration before empty-answer recovery side effects (retry state, warning, heartbeat and dispatch). Keep the existing cap and terminal error. Extend `tests/web-search/web-search-forced-declaration.test.ts` with search/search/search/empty at maxSearches=1: exactly four model attempts, one physical search, terminal cap error, no fifth dispatch. Also preserve recovery below the ceiling. Update `structure/runtime.md`; the fetch loop does not dispatch eagerly and needs no production change. +3. A3: modify only `logs.detail.reason.decode_window_too_short` in `gui/src/i18n/pt.ts` to describe the measured output window. Existing metric calculation and all other locales remain unchanged. + +No new persisted fields, enums or interfaces. The pending-LF flag is request-local parser state only; it is created false, set at consumed CR delimiter boundaries, consumed by the next nonempty fragment, and discarded with the relay. No serialization boundary exists. + +## Verification + +- Baseline: `bun test tests/responses/responses-terminal-repair.test.ts tests/web-search/web-search-forced-declaration.test.ts`; output in private recovery evidence. +- Red/green for added regressions, followed by `bun test tests/responses/responses-terminal-repair.test.ts tests/responses/sse-rewrite-line-endings.test.ts tests/responses/chat-sse-framing-guard.test.ts`. +- `bun test tests/web-search/web-search-forced-declaration.test.ts tests/web-search/web-search-run-turn-loop.test.ts`. +- Existing header/replay and generation-window focused regressions, including management metric consumers, within the eight-file batch limit. +- GUI locale tests with `--isolate` and `bun run lint:i18n`; no rendered layout is changed and no build is authorized. +- `bun run structure:check`, file-size ratchet, layout guards, privacy scan and `git diff --check`. +- Root/GUI typechecks and full cross-platform CI are deferred to the coordinator's frozen union. Tests only establish their exercised local/mock contracts. diff --git a/devlog/_plan/261003_train_recovery_d/000_verification.md b/devlog/_plan/261003_train_recovery_d/000_verification.md new file mode 100644 index 00000000000..c0b5a7b37e6 --- /dev/null +++ b/devlog/_plan/261003_train_recovery_d/000_verification.md @@ -0,0 +1,105 @@ +# Lane D recovery verification — 2026-10-03 + +The six accepted Lane D carries were reviewed at +`da40c734a558ea32ce391613f5a0309d14cfce81`. Focused tests and rendered fixture +scenarios found no demonstrated defect requiring a product-code repair. This +recovery adds only this verification record; it preserves the carries and their +contributor attribution. Final integration CI remains pending. + +## Accepted source + +| Source PR | Carry commit | Verified contract | +| --- | --- | --- | +| #6426 | `6d4e40443c451039f7d215743aea925852565c49` | Optimistic visibility, ordered writes, reconciliation and target cancellation | +| #6471 | `9d3e6e252a362dd01e756bb48b3af3a75225bf4d` | End-to-end output throughput uses token sums divided by duration sums | +| #6149 | `3e65384642dd6d02d558a425c1a78654cea24ce6` | Compaction source allowlists and conditional conversation disclosure | +| #6151 | `de828619f84659603f6a13740401049f28dd4dde` | Owned Droid defaults, explicit caller precedence and concrete-target validation | +| #6405 | `56ea6d76e36370bc341ec1592a9a290a459942d4` | Effective export metadata and managed OpenCode/Kilo refresh | +| #6458 | `5d7efbb749ffb72efa6696571b73a4a874a8c61d` | Brazilian Portuguese catalog, placeholders and auxiliary locale maps | + +All six commits are ancestors of the verified head. The train's existing +Portuguese zoom-key repair is included. No dropped item was revived: #6436 +retains its product hold, #6336 retains its credential-destination evidence +blocker, and #5253 remains superseded by #6458. + +## Executed verification + +Local macOS, Bun 1.4.0. Batches ran sequentially; full outputs were inspected. +The root command uses the repository test preload and sandbox: + +```sh +bun test ./tests/usage/usage-throughput.test.ts ./tests/responses/responses-compaction-override.test.ts ./tests/responses/droid-reasoning-defaults.test.ts ./tests/clients/droid-managed-reasoning-defaults.test.ts ./tests/server/management-droid-reasoning-defaults.test.ts ./tests/clients/client-export-effective-metadata.test.ts ./tests/clients/client-export-reasoning-controls.test.ts ./tests/clients/opencode-kilo-refresh.test.ts +``` + +Exit 0: **144 passed, 0 failed**, 1,175 assertions across eight files; no skips +reported. The GUI command ran from `gui/`: + +```sh +bun test --isolate ./tests/models-visibility-queue.test.tsx ./tests/usage-custom-range.test.tsx ./tests/compaction-routing-panel.test.tsx ./tests/integrations-surfaces.test.tsx ./tests/i18n-locales.test.ts ./tests/locale-parity.test.ts +``` + +Exit 0: **132 passed, 0 failed**, 4,920 assertions across six files; no skips +reported. `bun run structure:check` and `bun scripts/file-size-ratchet.ts` +also passed. Runtime locale tests confirm equal English/Portuguese key sets, +placeholder parity and registration; static extraction counted 3,769 keys each. + +## Rendered scenarios + +Real source components and production CSS ran through the existing Vite dev +server against fixture-only loopback responses. Vite inherited no proxy config; +unmatched fixture API calls failed closed. The browser used an isolated profile. +No user configuration, credentials, accounts or installed client files changed. + +- Models: repeated toggles displayed the latest intent while a write was held; + request records confirmed click order. Bulk changes reconciled, refused saves + restored authoritative state, and switching targets discarded the old draft. +- Usage: summary and tables displayed measured throughput and an unavailable + marker for missing telemetry. This metric is not decode speed. +- Compaction: a 350-model roster rendered a bounded subset. Selecting a search + result beyond the first 300 retained an existing hidden selection on save. +- Droid: the selected effort reached the review request, refused apply displayed + an error, and an external target change cleared the draft and confirmation. +- Portuguese: all four views were inspected at 390×844; document width stayed + within the viewport. Wide tables retained their local horizontal scrolling. + +Twelve screenshots were inspected, including English desktop states at 1440×1000. +Screenshots and raw logs remain outside tracked source for the integration review. +Browser execution resumed after fixture-only middleware ordering, searchbox +locator and modal-covered fixture-control corrections; it was not one uninterrupted +passing run. Passing portions were retained without repetition. Both the fixture +server and isolated browser were stopped and their loopback listeners closed. + +## Older review dispositions + +- **#6149, review5343376705:** `gui/src/i18n/ja.ts:466` and `:469` express retry + as a possibility. `gui/src/i18n/ru.ts:466` and `:469` identify target providers + as recipients of the conversation. Tests in + `gui/tests/compaction-routing-panel.test.tsx:261` and `:271` pin the conditional + Japanese meaning and both Russian scopes; all three cases passed. +- **#6151, review5341490252:** `src/server/chat-completions.ts:159` preserves the + default separately from explicit effort before translation; `:452` carries + that provenance into combo/policy dispatch. Literal validation occurs against + each target in `src/server/droid-reasoning-default.ts:25`, + `src/server/responses/core-combo.ts:719`, and + `src/server/responses/core-combo-native.ts:212`. The regression at + `tests/responses/droid-reasoning-defaults.test.ts:269` covers both empty and + incompatible nonempty first ladders across bridge, native and policy routes. + Ownership fingerprint checks in `src/integrations/state.ts:485` and the + managed-default tests reject edited or foreign rows. These cases passed. +- **#6426 scope comment:** the carry's English and Chinese dashboard-document + hunks contain only queue-behavior additions, without unrelated metadata or + reflow changes. Render evidence supports maintainer UI acceptance review; + this technical verification is not itself that approval. + +An inherited independent final reviewer read both focused logs, checked these +dispositions and fixture isolation, inspected representative screenshots, and +returned **PASS with no blocking findings**. No duplicate rewrite was needed. + +## Remaining acceptance + +Frozen-union root/GUI typechecks, production bundling and comprehensive +cross-platform CI remain coordinator-owned and pending. No full local suite, +dependency installation, native build or live provider check ran. Fixture page +rendering does not establish authenticated app-shell, packaged Tauri, real +Droid/OpenCode/Kilo wire behavior, or native-speaker Portuguese acceptance. +No security approval, release, deployment or issue closure is claimed. diff --git a/devlog/_plan/261003_train_recovery_d/010_late_review.md b/devlog/_plan/261003_train_recovery_d/010_late_review.md new file mode 100644 index 00000000000..eff3beb347a --- /dev/null +++ b/devlog/_plan/261003_train_recovery_d/010_late_review.md @@ -0,0 +1,56 @@ +# Lane D late-review follow-up + +This follow-up starts from landed dev `3ada270f3a0d955ad02b6a4f8f74b0412906bcb1`. +The earlier verification record describes its inspected scenarios, not a claim +that late review could find no further defect. Existing Doctor fixes remain intact. + +## Comment dispositions + +| Comment | Disposition | Evidence and change | +| --- | --- | --- | +| 4172062340 | Accepted | `gui/src/pages/integrations/FileIntegrationPage.tsx` reuses the omission-aware review path. No draft sends no defaults map; an explicit empty map still clears defaults. The GUI regression exercises Update and Save / review through preview and commit. | +| 4172062382 | Accepted | `src/clients/config-export/droid.ts` filters inherited defaults against the current normalized model's declared efforts. Refresh removes unsupported headers and preserves compatible peers. Regressions cover incompatible, empty and unknown ladders plus explicit clearing. Ownership checks are unchanged. | +| 4172062352 | Accepted | `gui/src/pages/Usage.tsx` shares a finite-number guard across summary, tables, tooltips and assistive text. Malformed rates are unavailable. Regressions cover null, strings and nonfinite values. | +| 4172062362 | Accepted | `gui/src/use-model-visibility.ts` treats JSON integration details as optional after a successful write. Empty/non-JSON success still reconciles against the catalog; transport failures retain their existing error path. Regressions cover 204 and text responses. | +| 4172062394 | Rebutted | `structure/dashboard-and-usage.md`, the English dashboard guide, `usage.throughput.title` and `src/usage/summary.ts` explicitly define qualifying **attempts**, with one sample for a legacy row without attempts. Changing this to distinct requests would change the accepted contract. No aggregation semantics changed. | +| 4172062396 | Accepted | `tests/usage/usage-throughput.test.ts` identifies the second request's model and asserts two model rows at 10 and 15 tok/s with one sample each, alongside the weighted provider/summary rate. | + +## Focused verification + +From `gui/`: + +```sh +bun test --isolate ./tests/models-visibility-queue.test.tsx ./tests/usage-custom-range.test.tsx +bun test --isolate ./tests/integrations-surfaces.test.tsx +``` + +The first command reproduced seven failures before the display/response fixes +(39 passed); after repair it passed **46/0**. The second passed **58/0**. +Droid regressions were added before its source edit but were not run red; +no Droid red-green claim is made. + +From the repository root: + +```sh +bun test ./tests/usage/usage-throughput.test.ts +bun test ./tests/clients/droid-managed-reasoning-defaults.test.ts ./tests/server/management-droid-reasoning-defaults.test.ts ./tests/responses/droid-reasoning-defaults.test.ts +``` + +These passed **5/0** and **44/0**, respectively. All batches were sequential; +none reported skips. Structure, file-size ratchet and privacy checks passed. + +Three fixture-browser regressions exercised malformed string throughput across +all three displays and a 204 visibility write through successful reconciliation. +The Droid no-draft review also opened confirmation with the defaults map omitted. +All passed; the throughput/visibility run had no uncaught page errors. Screenshots were inspected and +remain outside tracked source. An initial browser assertion also selected cache +cells; narrowing its selector to throughput corrected the harness, not the product. + +Broad typechecks, builds, full suites, Windows/native and real-client acceptance +remain unrun locally. Final PR/CI and production-readiness judgment belong to the +coordinator. No push, PR, merge, installation or release is part of this lane. + +Independent inherited review of the final source/tests/docs returned **PASS**, +with no blocking findings; it supported all five accepted fixes/test improvements +and the attempt-count contract rebuttal. This is a technical review, not final +production-readiness approval. diff --git a/devlog/_plan/261004_fixture_isolation_stabilization/000_plan.md b/devlog/_plan/261004_fixture_isolation_stabilization/000_plan.md new file mode 100644 index 00000000000..88a914f978b --- /dev/null +++ b/devlog/_plan/261004_fixture_isolation_stabilization/000_plan.md @@ -0,0 +1,27 @@ +# Repair fixture ownership without changing production lease safety + +The release test fixtures can share a wrapper-startup service authority, and one fixture kills its holder before releasing a young lock. A directory-blind injection snapshot also depends on the installed Codex binary. Repair the two fixtures while preserving the actual runtime and original assertions. Historical failures elsewhere receive controlled attribution, not a waiver from isolated passes or CI. + +Reader: coordinator deciding whether this bounded repair can join the final regression candidate. + +- Class/archetype: C3 fixture lifecycle and isolation; satisfy-spec, one PABCD cycle. +- Trigger/goal: delegated stabilization on dev dd9a980ec071285e628a35d8cdefaea785b8916b; publish a small test-only repair PR with exact-head evidence. +- Scope: tests/update/update-restart-lease.test.ts and tests/codex-integration/injection-model-suggest-routes.test.ts, plus this unit. Inherited leaf implementer owns lease fixture; main owns injection fixture and integration. Historical explorer writes only ignored scratch. Independent reviewer checks the integrated patch. +- Non-goals: production/runtime fixes, lease grace/reclaim changes, deleted/weakened assertions, skips/retry-as-fix, other B/E repairs, user-home/live-service/account changes, broad local suite/import graph, final all-platform CI, merge/release. +- Verifier: original focused files, holder→successor order, bounded forced-cleanup negative, case-owned authority checks, directory mutation sensitivity and cache cleanup; one bounded combined runner control; typecheck/privacy/structure/file-size plus applicable PR CI. No repeated GUI build/test: coordinator supplies its separate current-tree 43/0 proof. +- Stop: DONE means both fixtures repaired, focused/control evidence and independent review complete, own PR published with successful applicable exact-head CI, and truthful attribution/limits report. Unexplained historical producer identity remains explicitly unresolved; it is not silently counted as passing. +- Artifact: this numbered unit and ignored .tmp/release-stabilization/fixture-followup/report.md with raw command/HEAD/cleanup receipts. +- Resource/authority: existing tools, own checkout/branch, inherited leaves, shared user test lock. Owned TMP paths retain the existing shared runtime lock path. No token/time budget or new paid resources. Commands are bounded; no OCX_TEST_NO_QUEUE or foreign-lock removal. +- Escalation: a separately verified product defect, new write scope, or actual safety/access blocker. Main decides scope; coordinator merges. + +## Evidence and design decisions + +Baseline controlled original lease trio on dd9a980e: 1 pass / 2 fail, 8 assertions, 14.27s, same young-lock timeout signature. The coordinator separately traced the dead holder and toggled cooperative release: 1/2 to 3/0 without changing original assertions. Prior injection evidence identifies installed bundled discovery creating codex/tmp before the initial snapshot. Raw traces and hypotheses remain in ignored scratch. + +Architect: D1 case-scoped parent homedir spy and same child startup home, preserving real-home guard; D2 stdin EOF release/readiness/ack; D3 bounded reaping/drains and failed graceful release stays failure; D4 existing runtime/catalog cache seams plus recursive snapshot; D5 exactly two fixture files and no runtime edits; D6 original sequence and adversarial isolation controls. Main accepts all six. See 010 for concrete changes. Same-architect reflection and independent A audit precede edits. + +Historical explorer owns five remaining victim files (16 named failures). Controlled producer/victim/off/on experiments may establish equivalent contamination sufficiency; historical adjacency alone cannot identify the original writer. Source equivalence and isolated passes remain narrower evidence. No fixture expansion is authorized from that report alone. + +Architect reflection identified one accepted omission: injection environment capture must be per-case, not module-evaluation time. 010 now requires beforeEach capture, unconditional owner-cache reset and failure-safe env/root cleanup. Combined runner control explicitly uses one worker and serial cases. No design expansion. + +Implementation integration: all7 original lease expectations remain; The implementer repaired missing first-case port config using the existing second-case setup after observing an existing default-port proxy, then obtained12/0. Main added an awaited/bounded stdin end result so asynchronous pipe-close failure cannot escape teardown. Injection9/0 includes deterministic EISDIR red proof, recursive byte/directory observation and a stale module-capture control. No production source changes. diff --git a/devlog/_plan/261004_fixture_isolation_stabilization/010_fixtures.md b/devlog/_plan/261004_fixture_isolation_stabilization/010_fixtures.md new file mode 100644 index 00000000000..267a28459db --- /dev/null +++ b/devlog/_plan/261004_fixture_isolation_stabilization/010_fixtures.md @@ -0,0 +1,30 @@ +# Make each fixture own its state and child lifetime + +Depends on: approved 000 plan. No public type, stored schema, production dependency or runtime behavior changes. MODIFY only the two test files and this unit; no new layout registration. + +## Lease fixture: executor scope + +In tests/update/update-restart-lease.test.ts: + +1. MODIFY node:os import to namespace form and add Bun test spyOn. Before each case, capture environment and arrange a restorable homedir spy. sandbox() sets the spy to its case home before calling serviceStatePaths(). Replace platform-specific child HOME/USERPROFILE selection with box.home on every platform. Preserve the original OCX_REAL_HOME protection and token exclusion. Assert every parent candidate and the selected authority are within box.root, using path-relative containment; retain actual child-authority equality before service-child behavior. Pin/restore CODEX_SQLITE_HOME to the case if inherited override could escape fixture scope. +2. MODIFY child bookkeeping to track actual Bun subprocesses and their drain completion; keep fake Node ChildProcess only at the production injection seam. Do not cast Bun objects to Node types for cleanup. Preserve childLogs behavior and original assertions. +3. REPLACE the lease holder's `await Bun.sleep(13_000); lease.release();` with explicit stdin EOF protocol. Child acquires the real lease, writes a case-owned ready marker, then waits on its piped stdin. Closing stdin requests release; child calls release, writes a distinct released acknowledgment and exits normally. Parent keeps stdin open throughout the existing fail-closed assertions. The synchronous runService hook polls the ready marker under its existing bound, verifies holder ownership, then returns. Production reacquire 10s and stale-grace 30s remain unchanged. +4. MODIFY teardown: request cooperative release for holders, require acknowledgment/exit0/exact lock absence before root removal, and await output drains. Bound every wait with existing watchdog/budget helpers and clear timers. If cooperation fails, kill/reap only that tracked fixture child under a separate bound, settle/cancel its drains while collecting every observed rejection by drain index, exclude only the exact cleanup-owned cancellation object (not an error name), preserve all genuine and graceful failures and restore env/spies in finally. Remove an ordinary proven-owned root only after every child is reaped, drains settled and the full cleanup protocol succeeded; retain it with diagnostics on an unexpected cleanup failure. An explicit negative control may dispose its owned root after reaping/draining only after verifying and consuming exactly its deliberately expected failures. Never unlink/backdate a lock to obtain green. +5. ADD behavior controls in the same file: two cases yield different contained authority paths; parent/child authority matches with no service command; successful EOF release removes the exact lock and leaves no live child before root cleanup; ignored EOF/missing acknowledgment reaches bounded fallback and is reported as failed graceful cleanup, not success. Preserve all seven existing behavioral cases and assertions. Original holder→two successors sequence is the regression oracle. + +## Injection snapshot: main scope + +In tests/codex-integration/injection-model-suggest-routes.test.ts: + +1. MOVE environment capture from the module-level saved object into beforeEach, immediately before each case overrides. Capture every variable this fixture changes, then unconditionally reset both caches and restore that per-case snapshot through failure-safe cleanup before removing its owned root. ADD imports for existing reset/setCodexRuntimeResolveCacheForTests and reset/setBundledCatalogCacheForTests. After case environment setup and before first listCatalogNativeSlugs(), seed both with the same synthetic command/version, source fallback and `{ discoverAlternatives: false }` runtime key, following codex-convergence-account-selectors.test.ts:217. Use a supported native fixture row. This removes dependence on launching installed Codex while keeping the real catalog/listing/handler and writer paths. Reset both owners unconditionally before restoring environment/removing the case root. Do not introduce a module mock of the handler or snapshot it away. +2. REPLACE the one-level readdir/readFile snapshot with recursive lstat-based owned-tree traversal. Record directory markers (including empty directories) and exact regular-file bytes. Reject symlinks/nonregular entries explicitly rather than following them or ignoring a subtree. Retain only the actual top-level `ocx/codex-runtime.json` cache exclusion; nested same-basename files and codex-home files remain observable. Preserve `expect(snapshot()).toEqual(before)` and every existing route/model/effort/error assertion. +3. ADD fixture controls: unchanged pre-existing nested and empty directories snapshot successfully; nested file-byte mutation, empty-directory addition/removal and file/directory replacement change the snapshot; same-basename nested cache paths are not exempt. A safe owned symlink/junction fixture proves no traversal. Assert cache reset leaves no prior seeded memo after cleanup (matching the same runtime cache key). Seed immediately before synchronous fixture catalog use; no live model/discovery subprocess is required. + +## Verification and boundaries + +- Existing wrapped baseline trio already reproduced 1/2 on dd9a980e; do not duplicate baseline or launch the original unsafe service child before authority isolation. +- After repair: wrapped original lease trio, then complete original lease file plus new controls; complete injection file with original assertions plus directory/cache controls. Meaningful combined run uses only these two files and `--parallel=1` with no concurrent cases; retain observed runner order rather than assuming argv determines order. Explorer's no-service producer→victim→off/on control classifies other historical symptoms separately. +- Run `bun run typecheck`, `bun run privacy:scan`, `bun run structure:check`, relevant file-size guard and `git diff --check`. Test commands use .tmp/release-stabilization/fixture-followup/run.py to preserve shared user lock and own TMP, HEAD and child-exit receipts. No broad suite/import graph, no duplicate management-integration GUI test/build, no manual final lane=all/all-platform dispatch. +- Verify `git diff dd9a980e -- src` is empty and no original expectation was removed/weakened. Runtime lease tests remain real; no source guard bypass or altered timeout is accepted. +- Independent code/security reviewer checks fixture path ownership, guard retention, fallback failure propagation, restoration and process cleanup. Hosted per-PR CI must execute applicable jobs at the published head; platform absence/skips remain limits. +- No live service-manager operation or user-home mutation. Only case-owned transient test children/listeners permitted by the original tests after containment assertions pass. No Windows-native claim from macOS execution alone. diff --git a/docs-site/src/content/docs/fr/guides/claude-code.md b/docs-site/src/content/docs/fr/guides/claude-code.md index 3c66bdecaff..b923f756cd6 100644 --- a/docs-site/src/content/docs/fr/guides/claude-code.md +++ b/docs-site/src/content/docs/fr/guides/claude-code.md @@ -746,3 +746,17 @@ Sur toutes les routes Chat traduites, les rappels de l’historique conservent l ### `anthropicAccountPool.routes` Les règles `anthropicAccountPool.routes` limitent la sélection et les reprises 429 aux comptes enregistrés du premier modèle correspondant lorsque le pool est activé. Sans compte éligible, la requête échoue localement; `fallback: true` autorise alors le pool ordinaire. Les règles ne prouvent pas l’accès du compte au modèle. + +### Forced Claude Code subagent model + +The Subagents page offers **Force all subagents onto one model**, off by default. Select an exposed roster-style id, such as `combo/tev-auto`, then enable the switch. The roster is offered first; unavailable saved roster entries cannot be force targets. + +`ocx agent subagents force combo/tev-auto` sets `claudeCode.subagentModelForce`; `ocx agent subagents force -` clears it. `ocx agent status` reports the setting. `GET /api/subagent-models` returns `force`, `forceAvailable`, and `forceStatus`; `PUT` accepts `{ "force": "combo/tev-auto" }` or `{ "force": null }` without changing the roster. Omitting `force` leaves it unchanged. Invalid or unexposed targets are rejected on write; stale targets are reported and skipped at launch. + +This takes effect on the **next routed `ocx claude` launch**, injecting `CLAUDE_CODE_SUBAGENT_MODEL` as an explicit proxy alias (with `[1m]` only for an authoritative million-token window; native Claude targets use a reversible native alias) and `CLAUDE_CODE_SUBAGENT_MODEL_FORCE=1`. Each nonempty shell-exported variable independently wins. Native launches inject neither variable; plain `claude` is not affected. No plugin files or `settings.json` are modified by this setting. + +Claude Code **2.1.257 or newer** is required for FORCE. Plugin and built-in agents (including Explore/Plan) and per-call model arguments are overridden. Forks and subagent skills with `model: inherit` keep the main conversation model. The main loop and Haiku/small-fast sidecars are unaffected. Existing roster files remain available. + +The dashboard warns about old or unknown CLI versions, unavailable targets, and either variable already present in `settings.json` → `env` (which overrides launch env). Detection is read-only and server-local: it cannot inspect another launch shell, another machine, or project-local settings. An unknown result is not proof of force support. + +Explicit gateway selectors on a generated agent request take precedence over its legacy `ocx-route` fallback, even if the saved force setting changes after launch. For shell or settings overrides of generated roster agents, use an explicit gateway alias; bare Claude ids retain the older-client fallback behavior. Native aliases restore their bare model before the existing credential and model-map checks. Connected launches validate force targets against a fresh authenticated gateway catalog; failed discovery skips automatic force injection, and cached context windows alone never prove availability. diff --git a/docs-site/src/content/docs/fr/guides/integrations.md b/docs-site/src/content/docs/fr/guides/integrations.md index 7588d5526fe..83e6c7eebb2 100644 --- a/docs-site/src/content/docs/fr/guides/integrations.md +++ b/docs-site/src/content/docs/fr/guides/integrations.md @@ -198,7 +198,10 @@ une configuration cliente. **Pour `ocx opencode`, le bloc fournisseur du lanceur l'emporte.** Le lanceur injecte `provider.opencodex` par `OPENCODE_CONFIG_CONTENT`, qui est prioritaire sur la même entrée enregistrée sur -le disque ; le reste de votre configuration opencode continue de s'appliquer normalement. Le commutateur +le disque ; le reste de votre configuration opencode continue de s'appliquer normalement. Ces blocs sont +construits à partir des mêmes métadonnées canoniques effectives que les exports : capacités, choix de +raisonnement et limites connues sont repris tels quels, la limite de sortie recourant au besoin au +repli de 32000 exigé par le schéma, borné au contexte. Le commutateur décrit ici est celui qui compte lorsque vous lancez directement `opencode`. ## Depuis le terminal @@ -231,10 +234,12 @@ ocx mcode ``` Une fois l’intégration connectée, `ocx sync` et `POST /api/sync` actualisent les catalogues MCode, -Pi, Aside, Raycast et omo gérés. Le démarrage du proxy actualise aussi le catalogue Raycast géré. -Les changements de visibilité, de fournisseur ou de préréglage actualisent Pi, Aside, Raycast et omo. -Les blocs absents, modifiés par un tiers, non sûrs ou supprimés manuellement restent intacts ; -réactivez explicitement l’intégration lorsque vous souhaitez la reconnecter. +Pi, Aside, Raycast, omo, OpenCode et Kilo gérés. Le démarrage du proxy actualise aussi le +catalogue Raycast géré. Les changements de visibilité, de fournisseur ou de préréglage actualisent +Pi, Aside, Raycast, omo, OpenCode et Kilo. Les blocs absents, modifiés par un tiers, non sûrs ou +supprimés manuellement restent intacts ; réactivez explicitement l’intégration lorsque vous +souhaitez la reconnecter. Démarrez une nouvelle session Pi, OpenCode ou Kilo pour charger le +fichier actualisé. Le CLI distinct de la plateforme MiniMax (`mmx`) n’est pas une intégration à commutateur de fichier. Ses commandes textuelles utilisent le point de terminaison compatible avec Anthropic de MiniMax ; OpenCodex @@ -304,7 +309,7 @@ ocx integration client restore --op ## Kilo -Kilo n’écrit que `provider.opencodex` dans le premier fichier global existant sous `~/.config/kilo` (`XDG_CONFIG_HOME` déplace ce répertoire ; `kilo.jsonc` est créé si aucun candidat n’existe). Si un autre fichier candidat définit aussi `provider.opencodex`, l’état signale un conflit et Appliquer refuse. Les autres clés restent inchangées. Appliquer réécrit tout le fichier ; commentaires et virgules finales ne sont pas conservés. Sélectionnez `opencodex/` dans Kilo. +Kilo n’écrit que `provider.opencodex` dans le premier fichier global existant sous `~/.config/kilo` (`XDG_CONFIG_HOME` déplace ce répertoire ; `kilo.jsonc` est créé si aucun candidat n’existe). Si un autre fichier candidat définit aussi `provider.opencodex`, l’état signale un conflit et Appliquer refuse. Les autres clés restent inchangées. Les choix de raisonnement par modèle sont écrits sous forme de carte de variantes dans le bloc fournisseur, à partir des mêmes métadonnées canoniques effectives que l’export OpenCode. `ocx sync` et `POST /api/sync` actualisent un bloc Kilo possédé par OpenCodex ; démarrez une nouvelle session Kilo pour charger le fichier actualisé. Appliquer réécrit tout le fichier ; commentaires et virgules finales ne sont pas conservés. Sélectionnez `opencodex/` dans Kilo. Désactiver peut retirer le bloc appartenant à OpenCodex du fichier enregistré même si un autre candidat est en conflit ou ne peut pas être analysé ; cet autre fichier reste intact. diff --git a/docs-site/src/content/docs/fr/guides/remote-link.md b/docs-site/src/content/docs/fr/guides/remote-link.md index 6a619f8d5dd..52f45296021 100644 --- a/docs-site/src/content/docs/fr/guides/remote-link.md +++ b/docs-site/src/content/docs/fr/guides/remote-link.md @@ -11,7 +11,7 @@ Une liaison entre machines connecte un ordinateur OpenCodex **Home** à un ordin - Pour une liaison initiée par Child, Child peut se connecter à Home avec une clé OpenSSH (la connexion par mot de passe n’est pas prise en charge). - OpenCodex 2.66.0 ou ultérieur est installé sur Child (et sur Home pour une liaison initiée par Child). - Les deux ordinateurs utilisent macOS ou Linux. -- Le tableau de bord qui lance la liaison est ouvert sur cet ordinateur lui-même (navigateur ou application de bureau, installation autonome) ou via une session Hub appairée. +- Le tableau de bord qui ajoute un Child depuis Home est ouvert sur Home ou via une session Hub appairée. Transformer l’ordinateur actuel en Child exige une session de tableau de bord appairée par l’opérateur ; une session locale sans identifiant ne peut pas valider ce changement de routage. SSH par mot de passe et Windows restent hors du flux actuel. Une liaison peut être lancée des deux côtés : depuis Home, comme décrit ci-dessous, ou depuis Child, comme décrit dans la section « Connecter cet ordinateur comme Child ». @@ -25,6 +25,17 @@ SSH par mot de passe et Windows restent hors du flux actuel. Une liaison peut ê Le tableau de bord ne demande pas de saisir un jeton. Il sonde d’abord l’hôte et ne peut appliquer la liaison qu’après votre confirmation explicite de l’empreinte. +La fenêtre d’ajout Child rappelle que Child utilise les fournisseurs de ce Home via SSH et affiche les prérequis ci-dessus. Les alias viennent de `~/.ssh/config` sur le Home exécutant OpenCodex, pas forcément de l’ordinateur du navigateur. Si la recherche réussit sans hôte, ajoutez une entrée `Host` comme ci-dessous puis relancez la recherche. La saisie manuelle d’un alias existant reste possible ; sélectionner ou saisir un alias active le test de connexion. La fenêtre propose un lien vers ce guide. + +```sshconfig +Host devbox + HostName devbox.example.com + User you + IdentityFile ~/.ssh/id_ed25519 +``` + +Si la recherche échoue, la fenêtre affiche l’échec du chargement, la raison disponible et une action pour réessayer, sans annoncer une liste vide. Un échec du test conserve sa raison et l’indication SSH nettoyée dans la fenêtre active, sans doublon derrière elle. Consultez la raison et le diagnostic SSH ci-dessous, puis réessayez. Une nouvelle recherche conserve l’alias saisi mais exige une nouvelle vérification de l’empreinte avant connexion. + ## Connecter cet ordinateur comme Child Sur l’ordinateur qui doit utiliser les fournisseurs de Home : @@ -36,7 +47,9 @@ Sur l’ordinateur qui doit utiliser les fournisseurs de Home : La connexion redémarre OpenCodex sur cet ordinateur. Les tours Codex déjà en cours se terminent d’abord, et les nouvelles requêtes peuvent échouer pendant une minute au plus pendant le redémarrage. Le tableau de bord se recharge ensuite de lui-même et affiche la liaison Child. Codex continue d’utiliser `http://127.0.0.1:/v1` sur cet ordinateur, sans jeton ni variable d’environnement à définir : l’OpenCodex local relaie chaque requête vers Home, qui y répond avec ses propres fournisseurs et comptes. -Le rôle **Child** n’est disponible que lorsque OpenCodex tourne sur son port configuré, car Child redémarre exactement sur ce port. Si le tableau de bord indique qu’OpenCodex ne tourne pas sur son port configuré, redémarrez-le d’abord sur ce port. +Si **Child** demande de jumeler d’abord cet ordinateur, ouvrez son tableau de bord HTTP à l’adresse IP de bouclage configurée, par exemple `http://127.0.0.1:`. Le formulaire de jumelage local apparaît uniquement lorsque le jumelage manque et que le tableau de bord et l’API utilisent la même origine de bouclage. Copiez la commande `ocx gui pair --origin "http://127.0.0.1:"` du formulaire, exécutez-la dans un terminal sur cet ordinateur, puis collez le code à usage unique dans le formulaire. Utilisez exactement l’origine affichée ; une clé API de fournisseur ou un jeton d’administration n’est pas un code de jumelage. L’absence de jumelage et un port différent du port configuré sont deux causes distinctes. + +Le rôle **Child** exige également qu’OpenCodex fonctionne en mode autonome sur son port configuré, car Child redémarre exactement sur ce port. Si le tableau de bord indique qu’OpenCodex ne tourne pas sur son port configuré, redémarrez-le d’abord sur ce port. ## État de la liaison diff --git a/docs-site/src/content/docs/fr/reference/cli/agents.md b/docs-site/src/content/docs/fr/reference/cli/agents.md index f9db378b53d..66176a64ec8 100644 --- a/docs-site/src/content/docs/fr/reference/cli/agents.md +++ b/docs-site/src/content/docs/fr/reference/cli/agents.md @@ -301,3 +301,15 @@ L’identité ou le condensat décrit les fichiers au moment de l’observation, Inspectez et modifiez en toute sécurité la configuration OpenCodex validée. `show` et `get` masquent les secrets. Importer valide avant d'écrire et nécessite `--yes`. + +### Forced Claude Code subagent model + +The Subagents page offers **Force all subagents onto one model**, off by default. Select an exposed roster-style id, such as `combo/tev-auto`, then enable the switch. The roster is offered first; unavailable saved roster entries cannot be force targets. + +`ocx agent subagents force combo/tev-auto` sets `claudeCode.subagentModelForce`; `ocx agent subagents force -` clears it. `ocx agent status` reports the setting. `GET /api/subagent-models` returns `force`, `forceAvailable`, and `forceStatus`; `PUT` accepts `{ "force": "combo/tev-auto" }` or `{ "force": null }` without changing the roster. Omitting `force` leaves it unchanged. Invalid or unexposed targets are rejected on write; stale targets are reported and skipped at launch. + +This takes effect on the **next routed `ocx claude` launch**, injecting `CLAUDE_CODE_SUBAGENT_MODEL` as an explicit proxy alias (with `[1m]` only for an authoritative million-token window; native Claude targets use a reversible native alias) and `CLAUDE_CODE_SUBAGENT_MODEL_FORCE=1`. Each nonempty shell-exported variable independently wins. Native launches inject neither variable; plain `claude` is not affected. No plugin files or `settings.json` are modified by this setting. + +Claude Code **2.1.257 or newer** is required for FORCE. Plugin and built-in agents (including Explore/Plan) and per-call model arguments are overridden. Forks and subagent skills with `model: inherit` keep the main conversation model. The main loop and Haiku/small-fast sidecars are unaffected. Existing roster files remain available. + +The dashboard warns about old or unknown CLI versions, unavailable targets, and either variable already present in `settings.json` → `env` (which overrides launch env). Detection is read-only and server-local: it cannot inspect another launch shell, another machine, or project-local settings. An unknown result is not proof of force support. diff --git a/docs-site/src/content/docs/fr/reference/configuration/server.md b/docs-site/src/content/docs/fr/reference/configuration/server.md index 8dec2432524..d82a29a23ca 100644 --- a/docs-site/src/content/docs/fr/reference/configuration/server.md +++ b/docs-site/src/content/docs/fr/reference/configuration/server.md @@ -274,3 +274,15 @@ compte et la charge de travail prévus. ## Diagnostic réseau des quotas Codex Le champ `quotaRefresh` de la ligne du compte Codex principal décrit la récupération du quota, pas le quota restant ni les droits d’accès au modèle. Il peut être absent lorsque les données sont en cache ou qu’aucune récupération n’a eu lieu. La requête utilise l’environnement du service proxy en cours d’exécution, pas celui du terminal interactif. Sans `proxy`, l’environnement existant est conservé ; `"auto"` lit les paramètres HTTP/HTTPS statiques de Windows ou macOS au démarrage. Sur macOS, un proxy hérité empêche cette lecture. Sur macOS, un motif valide `*.` devient `.` : `foo.local` contourne le proxy pour `*.local`, `xlocal` non, et le nom racine `local` le contourne aussi. Les plages exactes `169.254/16`, `169.254.0.0/16` et `fe80::/10` sont ignorées avec un diagnostic : les adresses IP link-local passent par le proxy. Les autres plages CIDR, motifs glob et exceptions de noms simples refusent la découverte sans modifier l’environnement. Les adresses IP et `*` restent acceptés. PAC/WPAD, les paramètres SOCKS seuls et les changements à chaud ne sont pas pris en compte automatiquement. Un succès avec TUN ne valide pas à lui seul le chemin du proxy HTTP. Consultez [les commandes et les états en anglais](/reference/configuration/server/#codex-quota-network-diagnostics). + +### Forced Claude Code subagent model + +The Subagents page offers **Force all subagents onto one model**, off by default. Select an exposed roster-style id, such as `combo/tev-auto`, then enable the switch. The roster is offered first; unavailable saved roster entries cannot be force targets. + +`ocx agent subagents force combo/tev-auto` sets `claudeCode.subagentModelForce`; `ocx agent subagents force -` clears it. `ocx agent status` reports the setting. `GET /api/subagent-models` returns `force`, `forceAvailable`, and `forceStatus`; `PUT` accepts `{ "force": "combo/tev-auto" }` or `{ "force": null }` without changing the roster. Omitting `force` leaves it unchanged. Invalid or unexposed targets are rejected on write; stale targets are reported and skipped at launch. + +This takes effect on the **next routed `ocx claude` launch**, injecting `CLAUDE_CODE_SUBAGENT_MODEL` as an explicit proxy alias (with `[1m]` only for an authoritative million-token window; native Claude targets use a reversible native alias) and `CLAUDE_CODE_SUBAGENT_MODEL_FORCE=1`. Each nonempty shell-exported variable independently wins. Native launches inject neither variable; plain `claude` is not affected. No plugin files or `settings.json` are modified by this setting. + +Claude Code **2.1.257 or newer** is required for FORCE. Plugin and built-in agents (including Explore/Plan) and per-call model arguments are overridden. Forks and subagent skills with `model: inherit` keep the main conversation model. The main loop and Haiku/small-fast sidecars are unaffected. Existing roster files remain available. + +The dashboard warns about old or unknown CLI versions, unavailable targets, and either variable already present in `settings.json` → `env` (which overrides launch env). Detection is read-only and server-local: it cannot inspect another launch shell, another machine, or project-local settings. An unknown result is not proof of force support. 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 8f0e9a10215..eba9d89fd46 100644 --- a/docs-site/src/content/docs/fr/reference/management-api.md +++ b/docs-site/src/content/docs/fr/reference/management-api.md @@ -422,3 +422,15 @@ Anthropic OAuth uniquement. `{ provider: "anthropic", accountId, threshold }` : Le DTO inclut `autoSwitchThresholdOverride` (entier/null), `autoSwitchThreshold` (défaut du pool) et `effectiveAutoSwitchThreshold`. 0 désactive seulement le basculement selon l’utilisation ; pause et reprise après 429 restent actives. HTTP: 400 invalid/unsupported; 404 missing account; `oauth_mutation_busy` on lock contention. + +### Forced Claude Code subagent model + +The Subagents page offers **Force all subagents onto one model**, off by default. Select an exposed roster-style id, such as `combo/tev-auto`, then enable the switch. The roster is offered first; unavailable saved roster entries cannot be force targets. + +`ocx agent subagents force combo/tev-auto` sets `claudeCode.subagentModelForce`; `ocx agent subagents force -` clears it. `ocx agent status` reports the setting. `GET /api/subagent-models` returns `force`, `forceAvailable`, and `forceStatus`; `PUT` accepts `{ "force": "combo/tev-auto" }` or `{ "force": null }` without changing the roster. Omitting `force` leaves it unchanged. Invalid or unexposed targets are rejected on write; stale targets are reported and skipped at launch. + +This takes effect on the **next routed `ocx claude` launch**, injecting `CLAUDE_CODE_SUBAGENT_MODEL` as an explicit proxy alias (with `[1m]` only for an authoritative million-token window; native Claude targets use a reversible native alias) and `CLAUDE_CODE_SUBAGENT_MODEL_FORCE=1`. Each nonempty shell-exported variable independently wins. Native launches inject neither variable; plain `claude` is not affected. No plugin files or `settings.json` are modified by this setting. + +Claude Code **2.1.257 or newer** is required for FORCE. Plugin and built-in agents (including Explore/Plan) and per-call model arguments are overridden. Forks and subagent skills with `model: inherit` keep the main conversation model. The main loop and Haiku/small-fast sidecars are unaffected. Existing roster files remain available. + +The dashboard warns about old or unknown CLI versions, unavailable targets, and either variable already present in `settings.json` → `env` (which overrides launch env). Detection is read-only and server-local: it cannot inspect another launch shell, another machine, or project-local settings. An unknown result is not proof of force support. diff --git a/docs-site/src/content/docs/guides/chatgpt-desktop.md b/docs-site/src/content/docs/guides/chatgpt-desktop.md index dfa3f62725b..761862d2ea3 100644 --- a/docs-site/src/content/docs/guides/chatgpt-desktop.md +++ b/docs-site/src/content/docs/guides/chatgpt-desktop.md @@ -74,11 +74,23 @@ bundled binary of the discovered bundle; if that bundle has no app-server binary Before writing the launcher, `launch` also checks that the bundle and its app-server binary are owned by you or root, are not writable by group or others, and pass strict code-signature verification under OpenAI's team ID -(`2DC432GLL2`). A bundle that fails any of these is refused, so a copy placed by -another account cannot be made to run inside your session. The launcher file is +(`2DC432GLL2`). A bundle that fails any of these checks is refused, including +one owned by another account. The launcher file is written to a temporary file and renamed into place; an existing symbolic link at that path is replaced, not followed. +`restore` applies the same ownership, permissions, and signature checks to the +bundle and its main app executable before quitting or opening it. It works with +the experimental flag off and without a bundled app-server binary. If trust +verification or relaunch fails, the existing launcher is kept for recovery. + +Both commands also check the folders containing the bundle up to the filesystem +root. Folders owned by another account, symbolic links, and ordinary group- or +world-writable parents are refused. Root-owned administrator-group installation +folders and trusted sticky folders retain their normal permissions behavior. +These are ownership, POSIX-permission, and signature checks; native ACL and +volume ownership-policy behavior has not been verified. + This integration installs no certificate, network listener, PAC, or background watcher. It does not log the app's messages or environment. Status reports whether the running ChatGPT bundle process carries the expected launcher override. diff --git a/docs-site/src/content/docs/guides/claude-code.md b/docs-site/src/content/docs/guides/claude-code.md index 1a7953a8df0..376bc472166 100644 --- a/docs-site/src/content/docs/guides/claude-code.md +++ b/docs-site/src/content/docs/guides/claude-code.md @@ -255,6 +255,8 @@ trust a local certificate authority in the login keychain. That authority is con and its subdomains. Its signing key exists only inside the running OpenCodex process, so every OpenCodex restart publishes a fresh authority and macOS asks you to trust it again — approve the prompt, or later run `ocx claude desktop picker trust`, after each restart. +Startup also attempts to remove a legacy on-disk picker signing key before checking whether +interception is enabled. Cleanup is best-effort and does not enable interception or block startup. On restart OpenCodex first removes the previous authority from the keychain. If that removal fails (for example because you decline the keychain prompt), the picker stays off for this run so two @@ -271,7 +273,9 @@ or turn it off with `ocx claude desktop picker off`. The dashboard has the same **Claude → Desktop**. After the picker profile is selected, fully quit and reopen Claude Desktop. Picker mode is part of first-party mode, so the [first-party account risk](#first-party-opt-in) -applies to it as well. +applies to it as well. Desktop and CLI catalog rewrites share bounded row and metadata limits: +if adding routed models would exceed a limit, OpenCodex returns the original Anthropic catalog +unchanged rather than publishing a partial list. ### Use opencodex models from the Desktop Code tab (first-party bindings) @@ -313,8 +317,9 @@ The UI distinguishes uncertainty about whether settings still point at its proxy [Model picker in the CLI](#model-picker-in-the-cli). You can also bind a built-in Anthropic model id to a route (`ocx claude desktop bind`, above) or use `modelMap`. - `ANTHROPIC_SMALL_FAST_MODEL` and `CLAUDE_CODE_SUBAGENT_MODEL` are chosen by the CLI before the - request is sent; set them in `settings.json` yourself if a sidecar or subagent should use a - mapped id. + request is sent. Plain first-party `claude` uses your `settings.json` values. Routed + `ocx claude` also offers the [subagent force setting](#forced-claude-code-subagent-model) + on the Subagents page and through the CLI. - `ocx claude` and first-party coexist: a session started with `ocx claude` talks to `ANTHROPIC_BASE_URL` (plain HTTP on loopback), which `HTTPS_PROXY` does not cover, so that process reaches OpenCodex directly and the proxy simply sees no traffic from it. @@ -596,6 +601,15 @@ Three config states: The compaction value is adjustable on the Claude page. **Warning:** raising it past a model's real window breaks that model — the chat errors out before the summary can fire. +A model's advertised context window does not guarantee that a tool-heavy request fits the +upstream input limit. A classified input-limit rejection reaches Claude Code as +`invalid_request_error` with `context_length_exceeded`; a non-streaming response uses HTTP 400 +instead of a retryable 502. Reduce the current input or compact earlier. If `/compact` also +exceeds the limit, preserve the original history and try compacting a fork with fewer enabled +tool or MCP schemas, if your client supports that workflow. Recovery still depends on the +reduced request fitting the upstream limit. A `[1m]` marker or larger client accounting setting +does not raise that limit, and OpenCodex does not silently remove history or tools to make it fit. + Sub-1M native Anthropic models are never auto-marked. Values you export yourself always win (the proxy uses YOUR value to decide which models are safe to mark). Invalid hand-edited config values fall back to 829,800. @@ -634,8 +648,12 @@ Proxy startup/ensure, `ocx claude`, and relevant dashboard saves sync your featu overwritten or pruned; your own agents are never touched. - Files are atomically synced per file (write + rename). - `enabled: false` or `injectAgents: false` prunes all verified-owned definitions. +- Successful CLI first-party saves and persisted Desktop first-party setup also attempt best-effort + roster sync, including repeated setup to repair missing definitions. Refused setup and rollback + do not sync agents. Turning first-party off alone does not disable agent registration. - GUI PUT and roster changes resync immediately; every foreground or background proxy start/ensure - reconciles the owned files before a later Claude Code launch reads them. + reconciles the owned files before a later Claude Code launch reads them. Start a new Claude session + after registration so it loads the generated definitions. Dispatch: `subagent_type: "ocx-gpt-5-6-sol"`. 1M-capable targets carry `[1m]` automatically. @@ -961,3 +979,17 @@ the confirmed obsolete token file and applying first-party mode again; never del ### First-party picker context markers The Desktop Code-tab picker adds `[1m]` to routed models whose authoritative context window is at least one million tokens, so Claude uses its 1M accounting instead of the smaller custom-model fallback. Labels, profile order, and provider routes stay unchanged. Unknown and sub-million windows remain unmarked, including native long-window opt-ins: the picker cannot guarantee that a Desktop or remote runner receives the matching compaction environment. The paired auto-context setup for `ocx claude` is unchanged. An existing conversation keeps its saved selector until you select the model again from the refreshed picker. + +### Forced Claude Code subagent model + +The Subagents page offers **Force all subagents onto one model**, off by default. Select an exposed roster-style id, such as `combo/tev-auto`, then enable the switch. The roster is offered first; unavailable saved roster entries cannot be force targets. + +`ocx agent subagents force combo/tev-auto` sets `claudeCode.subagentModelForce`; `ocx agent subagents force -` clears it. `ocx agent status` reports the setting. `GET /api/subagent-models` returns `force`, `forceAvailable`, and `forceStatus`; `PUT` accepts `{ "force": "combo/tev-auto" }` or `{ "force": null }` without changing the roster. Omitting `force` leaves it unchanged. Invalid or unexposed targets are rejected on write; stale targets are reported and skipped at launch. + +This takes effect on the **next routed `ocx claude` launch**, injecting `CLAUDE_CODE_SUBAGENT_MODEL` as an explicit proxy alias (with `[1m]` only for an authoritative million-token window; native Claude targets use a reversible native alias) and `CLAUDE_CODE_SUBAGENT_MODEL_FORCE=1`. Each nonempty shell-exported variable independently wins. Native launches inject neither variable; plain `claude` is not affected. No plugin files or `settings.json` are modified by this setting. + +Claude Code **2.1.257 or newer** is required for FORCE. Plugin and built-in agents (including Explore/Plan) and per-call model arguments are overridden. Forks and subagent skills with `model: inherit` keep the main conversation model. The main loop and Haiku/small-fast sidecars are unaffected. Existing roster files remain available. + +The dashboard warns about old or unknown CLI versions, unavailable targets, and either variable already present in `settings.json` → `env` (which overrides launch env). Detection is read-only and server-local: it cannot inspect another launch shell, another machine, or project-local settings. An unknown result is not proof of force support. + +Explicit gateway selectors on a generated agent request take precedence over its legacy `ocx-route` fallback, even if the saved force setting changes after launch. For shell or settings overrides of generated roster agents, use an explicit gateway alias; bare Claude ids retain the older-client fallback behavior. Native aliases restore their bare model before the existing credential and model-map checks. Connected launches validate force targets against a fresh authenticated gateway catalog; failed discovery skips automatic force injection, and cached context windows alone never prove availability. diff --git a/docs-site/src/content/docs/guides/codex-integration.md b/docs-site/src/content/docs/guides/codex-integration.md index e0b148927d2..114376d265b 100644 --- a/docs-site/src/content/docs/guides/codex-integration.md +++ b/docs-site/src/content/docs/guides/codex-integration.md @@ -244,7 +244,9 @@ Remote and generic API clients retain the provider's hosted response format. Partial previews and URL-only results are not rendered by this compatibility layer. Artifacts use the existing retention limit, so save images you want to keep before older files are pruned. An invalid image or a failed local write produces a visible -failure message instead of a broken image link. +failure message instead of a broken image link. Display batches reject duplicate image +identities, oversized routing metadata, and output that exceeds the display byte limit; +these failures return HTTP 502 before streaming or a failed terminal event after it starts. ### Built-in image generation (`image_gen`) diff --git a/docs-site/src/content/docs/guides/combos.md b/docs-site/src/content/docs/guides/combos.md index e68510d4718..8a294b7563a 100644 --- a/docs-site/src/content/docs/guides/combos.md +++ b/docs-site/src/content/docs/guides/combos.md @@ -505,6 +505,7 @@ Combo failures are divided into **hop** failures and **terminal** failures. | HTTP 410 with an explicit model end-of-life, retired, deprecated, sunset, decommissioned, or no-longer-available signal | Cool that target and hop. Unrelated 410 responses remain terminal. | | Classified authentication, subscription, quota, rate-limit, overload, or upstream-server error | Cool the target and hop, even when the status alone is not sufficient. | | Client cancellation (499), `origin_rejected`, cyber-policy refusal, context overflow, or other invalid request | Stop and return the error; another target would not make the request valid. | +| Exact HTTP 400 saying a model is not supported when using Codex with a ChatGPT account | Try the next declared target before output commitment. The message must be the whole refusal in `detail`, `error.message`, or bare text; competing `detail` and `error` envelopes stay terminal. This does not quarantine the account or cool every model on the provider. | | Structured HTTP 400 rejecting optional `user`, an unsupported reasoning effort, or model-scoped image input | Hop before output commitment without cooling; see request-local target compatibility below. | | First tool call of a Responses turn run by an in-process adapter (`runTurn`) that the current request did not declare, before any output or replay-unsafe side effect | Cool the target and hop with the same tool catalog. After visible output or a replay-unsafe side effect the refusal is final. Chat Completions and Anthropic Messages requests are unchanged. | | Any other unclassified error | Stop and return the error. | diff --git a/docs-site/src/content/docs/guides/desktop-app.md b/docs-site/src/content/docs/guides/desktop-app.md index 03116c3125d..c2daf6ae260 100644 --- a/docs-site/src/content/docs/guides/desktop-app.md +++ b/docs-site/src/content/docs/guides/desktop-app.md @@ -66,6 +66,11 @@ if it fails, use **Retry** to resolve the current runtime again. On macOS, closing the dashboard keeps the app running in the menu bar. Open OpenCodex again from Dock or Finder to restore the dashboard without restarting the proxy. +On Windows, **Start at Login** quotes the executable path in the current-user startup +registration, including installations under `Program Files`. Previously enabled +registrations are updated once on launch. Startup entries you disabled in the tray +or Task Manager remain disabled. + ## Startup safety on macOS Startup safety reports **Desktop app** protection when OpenCodex's recorded ownership, @@ -78,6 +83,16 @@ and launcher installation or repair stays disabled while that ownership remains; Normal desktop updates replace the bundled CLI with the fixed startup probe. No local patch needs to be preserved across an update. +## Zoom + +On macOS and Linux, Cmd (macOS) or Ctrl (Linux) with `+`, `-` and `0`, or Ctrl with the mouse wheel, +zooms the window between 50% and 300% in 10% steps, and `0` returns to 100%. The sidebar shows the +same level with `-`, `+` and a reset button beside the theme and language rows. The level is +remembered and applied again the next time the app starts. The dashboard comes from the proxy the +app is attached to, so an app attached to an older proxy keeps the earlier behaviour (20% steps, not +remembered, no sidebar control) until that proxy is updated. Windows uses the browser engine's own +zoom, which is not remembered. + ## Keeping the proxy running The app keeps the proxy it started running. When that proxy restarts itself — after diff --git a/docs-site/src/content/docs/guides/integrations.md b/docs-site/src/content/docs/guides/integrations.md index b4488b926d8..6c009571f78 100644 --- a/docs-site/src/content/docs/guides/integrations.md +++ b/docs-site/src/content/docs/guides/integrations.md @@ -56,10 +56,31 @@ Keep your chosen `modelProfile.default` to apply it when plain `gjc` starts. The GJC models with a supported reasoning-effort ladder export `reasoning: true`, `thinking.levels`, and `compat.supportsReasoningEffort`, so GJC can offer an effort choice. Native Codex models receive their standard ladder even when the catalog omits it. Models without a known ladder omit these fields; `none` and `ultra` are not offered because `none` sends no effort and `ultra` folds to `max` on the wire. Refresh the integration to update these model options. The managed OpenCode integration owns two fragments: `provider.opencodex` (opencode V1) and -`providers.opencodex` (opencode V2). Only the V2 block carries the per-model reasoning-effort -variants, so both are written and kept in sync; they name the same provider and model ids, and -opencode V2 merges them into one provider entry. Apply, Refresh, Disable, and Restore act on both -fragments, and your other providers, agents, keybinds, and MCP entries stay untouched. +`providers.opencodex` (opencode V2). Both carry the same declared per-model reasoning choices and +defaults — the legacy block spells a model's default in its options and its choices as a variant +map, the V2 block uses the model's settings and a native variant array — so both are written and +kept in sync; they name the same provider and model ids, and opencode V2 merges them into one +provider entry. Apply, Refresh, Disable, and Restore act on both fragments, and your other +providers, agents, keybinds, and MCP entries stay untouched. + +Both fragments are built from the effective canonical model metadata — as are the Kilo export and +the `ocx opencode` launcher. An explicit custom override always wins, and a ladder you cleared to +empty stays empty; a reasoning ladder or default is never invented for a model that declares none. +An authoritative context limit is carried with an output budget beside it: a known output value — +explicit or from catalog metadata — wins, otherwise the schema-required 32000 fallback clamped to +the context, and without an authoritative context the whole limit block is omitted. Capabilities, +effort ladders, defaults, and an optional input limit appear only when known; the V2 block's +native capabilities object requires a known tools value, so when tool support is unknown it is +omitted entirely rather than written partially, and the legacy modality declarations stand. +"No ladder invented" still writes the controls, never choices: the V2 block always carries an +explicit variant array — empty when nothing is declared, because omitting it would make OpenCode +synthesize low/medium/high — and a model with known fixed reasoning but no adjustable ladder gets +disabled-only suppression instead of an effort: the legacy OpenCode block and Kilo both disable +every rung id the client would otherwise generate. Neither adds a selectable effort. The +selectable variants override the per-model +default — `none` included, offered only when the model's own declared ladder contains it. Whatever +the client selects, the proxy's pinned upstream reasoning policy still governs the request that +leaves it. Managed DSH support has a compatibility floor of **DSH 0.1.0-rc.6**. OpenCodex owns only `llm-pi-ai.providers.opencodex`; Apply and Refresh replace that fragment, Disable removes only that @@ -299,7 +320,9 @@ into any client config. **For `ocx opencode`, the launcher's provider blocks win.** That launcher injects `provider.opencodex` and `providers.opencodex` through `OPENCODE_CONFIG_CONTENT`, which outranks the same entries on disk — the rest of your opencode config still applies as -usual. The switch here is what matters when you launch `opencode` directly. +usual. Those blocks are built from the same effective canonical model metadata as the +exports, so the launcher and the managed integration describe the same models, limits, and +reasoning choices. The switch here is what matters when you launch `opencode` directly. ## From the terminal @@ -331,18 +354,18 @@ ocx mcode ``` Once connected, `ocx sync` and `POST /api/sync` refresh owned MCode, Pi, Aside, -Raycast, and omo catalogs with the current model selection, context windows, and +Raycast, omo, OpenCode, and Kilo catalogs with the current model selection, context windows, and reasoning-effort ladders. Proxy startup refreshes an owned Raycast catalog. Changes to model visibility, provider selection, or presets also refresh connected Pi, Aside, -Raycast, and omo catalogs. +Raycast, omo, OpenCode, and Kilo catalogs. Missing, foreign-edited, or unsafe blocks stay untouched, as do previously owned blocks you removed manually. An enabled Aside profile is an exception to the usual owned-only refresh: if its account directory exists and it has never had an owned block, sync may create its first block when that slot is empty. A prior Aside connection enables this behavior for all registered profiles by default. Sync does not create missing account directories or replace manual blocks. -A refused or overlapping refresh is reported separately for each client. Start a new Pi -session or fully quit and reopen Aside to load the updated file. +A refused or overlapping refresh is reported separately for each client. Start a new Pi, +OpenCode, or Kilo session or fully quit and reopen Aside to load the updated file. Aside refresh requires a [compatible running proxy](#aside-profile-controls). If Models reports **“Model selection saved”** together with a client-refresh warning, the @@ -545,7 +568,8 @@ press Save: - The same value is written to `codex.agents..model` in `~/.omo/omo.jsonc`, which LazyCodex 5.1.1 and later reads. If that file does not exist it is not created. If it contains comments it is left untouched, because saving would remove them; the tab says so, and you can - set the value there by hand. + set the value there by hand. Symlinks and non-regular files are rejected; on macOS and Linux, + a FIFO is rejected without waiting for a writer. A skipped mirror does not undo the role-file save. Nothing happens until you press Save; syncing or restarting opencodex never changes a role file. New Codex sessions pick up the change. The same controls exist on the command line: @@ -600,14 +624,18 @@ while another candidate conflicts or cannot be parsed; the other candidate is le The owned fragment is only `provider.opencodex` (OpenCode V1 shape: `npm`, `options`, `models`). Kilo's published schema has no OpenCode V2 `providers` key, so that block is -not emitted. `$schema`, `model`, `enabled_providers`, MCP, and other keys stay +not emitted; Kilo instead receives the per-model reasoning choices as a variant map inside +its provider block, built from the same effective canonical metadata as the OpenCode +export. `$schema`, `model`, `enabled_providers`, MCP, and other keys stay user-owned. Select `opencodex/` in Kilo after applying. Loopback uses `{env:OPENCODEX_KILO_API_KEY}` as `options.apiKey`. A non-loopback bind moves admission to `options.headers["x-opencodex-api-key"]` and never serializes a real key. Apply rewrites the whole global file as pretty JSON, so comments and trailing -commas in other keys are not preserved. Kilo is not on the implicit catalog fan-out; -refresh it explicitly after changing the routed model selection. +commas in other keys are not preserved. Kilo is on the implicit catalog fan-out: `ocx +sync`, `POST /api/sync`, and visibility, provider, or preset changes refresh an owned Kilo +block under the same safe owned-only rules as every other client, and a new Kilo session +loads the updated file. ```bash ocx integration client enable --client kilo @@ -623,6 +651,32 @@ Chat Completions endpoint. Choose a row from Droid's `/model` picker. Disable removes the managed rows; Undo restores the exact saved file. Other settings and custom models remain yours. +Open **Integrations → Factory Droid** (`/#integrations/droid`) to set a reasoning +default for each connected model. Choose from the model's supported efforts, +review the changes, then confirm. **No default** clears that model's draft setting; +use **Save / review changes** and confirm to apply the removal. +Models without a declared effort list show that no default is available. + +The default applies only when Droid omits an effort from its request. An explicit +request effort takes precedence over this default; existing OpenCodex pins and +caps still apply. Droid may continue to display **Dynamic** even when OpenCodex +applies the configured default. Requests without the Droid default header are +unaffected; the header is a request preference, not proof of client identity. +For combos and routing policies, each concrete target checks the preference +against its own effort list, so an incompatible first target does not remove it +from a compatible fallback. + +A saved default that the routed model no longer supports is ignored for requests. +If the connected model's declared effort list no longer includes the saved value, +it is omitted from the panel's defaults and removed from the managed row on refresh. +Reviewing without editing lets OpenCodex preserve the remaining supported defaults. + +Refresh preserves defaults while the exact `provider/model` selector remains +connected and declares the saved effort. Renaming a provider, model, or combo alias replaces that managed row +and clears its default; choose a default for the renamed row again. Disable +removes the defaults with the managed model rows, and Undo restores the saved +rows and their defaults together. + Models whose IDs or display names contain `,` or `]` are skipped because the managed selector cannot address them safely; export and managed settings show the same rows. A nonempty catalog with no addressable models is refused. diff --git a/docs-site/src/content/docs/guides/minimax.md b/docs-site/src/content/docs/guides/minimax.md index b09247dabfc..03a837406e8 100644 --- a/docs-site/src/content/docs/guides/minimax.md +++ b/docs-site/src/content/docs/guides/minimax.md @@ -12,6 +12,11 @@ the protocol boundary it actually exposes: ## MiniMax Code +In the OpenCodex Models page, both MiniMax Coding Plan regions advertise image input for +`MiniMax-M3` and `MiniMax-M3.1-Flash-Preview`. A Combo's **Image / multimodal** switch is +available only when every selected model supports image input. The switch controls image +attachments; it does not enable video uploads. + Install and sign in to MiniMax Code using MiniMax's instructions first. Then start OpenCodex and connect the reversible file integration: diff --git a/docs-site/src/content/docs/guides/model-routing.md b/docs-site/src/content/docs/guides/model-routing.md index acb84ddd43e..4ccfde4b97a 100644 --- a/docs-site/src/content/docs/guides/model-routing.md +++ b/docs-site/src/content/docs/guides/model-routing.md @@ -143,3 +143,14 @@ Routing and catalog visibility are separate controls: name (e.g. `anthropic` or `groq`) is actually configured. See [Configuration](/reference/configuration/) for the provider fields these rules read. + + +## Structured output on Cursor routes + +Cursor routes translate Responses `text.format` (`json_object` or `json_schema`) into explicit +final-answer instructions in the system context and active request, including tool-result +continuations. This is a prompt fallback: Cursor does not provide native constrained JSON decoding +on this transport, and models can still return an invalid answer. The adapter does not turn prose +into an approval decision. Auto-review callers should validate the returned JSON and use a +structured-output-capable route when strict enforcement is required. Ordinary text requests and +intermediate tool calls keep their existing behavior. diff --git a/docs-site/src/content/docs/guides/opencode.md b/docs-site/src/content/docs/guides/opencode.md index 09af3339c78..cc61a911c53 100644 --- a/docs-site/src/content/docs/guides/opencode.md +++ b/docs-site/src/content/docs/guides/opencode.md @@ -17,8 +17,8 @@ ocx opencode This ensures the proxy is running and launches opencode with the generated `provider.opencodex` and `providers.opencodex` blocks injected for that process — the -legacy spelling opencode V1 reads, and the V2 spelling that carries the reasoning-effort -variants. Extra arguments pass through: +legacy spelling opencode V1 reads, and the native V2 spelling. Both carry reasoning-effort +variants in the format their client reads. Extra arguments pass through: `ocx opencode run "hello"`. Routed models appear in the picker under the `opencodex` provider: @@ -31,32 +31,34 @@ opencodex/gpt-5.6-sol # native slugs stay unprefixed ## Reasoning effort opencode exposes reasoning effort as model *variants*. opencodex writes one variant per -declared effort for every model that advertises a ladder — `none` is skipped, because the -chat ingress has no wire effort for it — so the effort is selectable in opencode's model -picker instead of being pinned by the proxy. +declared effort, including `none` when the model explicitly supports disabling reasoning. +The selection reaches the proxy as a reasoning effort, not just a picker label. Existing +upstream effort pins in opencodex still apply. Two provider blocks are generated for this: | Block | Read by | Carries variants | |---|---|---| -| `provider.opencodex` | opencode V1 (`npm` + `options`) | no | -| `providers.opencodex` | opencode V2 (`package` + `settings`) | yes | +| `provider.opencodex` | opencode V1 (`npm` + `options`) | legacy options map | +| `providers.opencodex` | opencode V2 (`package` + `settings`) | native settings array | -Only the V2 spelling applies `variants`; a variant written under the legacy block is parsed -and then dropped, which is why both blocks are emitted. They name the same provider and -model ids, and opencode V2 merges them into a single provider entry, so no model appears -twice in the picker. Models with no declared effort ladder carry no `variants` key at all. +V2 uses its native variant array rather than the legacy map. The blocks name the same provider +and model IDs and merge into one provider entry. No model appears twice. V2 always writes a +variant array, empty when no choices are declared, to suppress automatic low/medium/high +choices. Known fixed-depth reasoners in the legacy block disable every automatically generated +rung for the same reason; those disabled entries are not selectable efforts. -No model-level default effort is written. The proxy keeps applying its own configured -default whenever a request carries no effort, so a default you change in opencodex stays -in force instead of being frozen into the config. +A known model default is exported; selecting a variant overrides that client default. +No default is invented when the catalog declares none. The launcher regenerates these +settings each time; refresh a managed integration or re-export a manual snapshot after +changing the default in opencodex. ## Images and attachments opencode decides whether a model takes an image from the model entry itself, and it cannot ask models.dev about `opencodex` — this provider is not there. The generated blocks therefore -carry opencode's own per-model capability fields, `attachment` and `modalities`, taken from -the metadata the proxy reports at `GET /api/models`: +carry per-model capability fields taken from the effective metadata the proxy reports at +`GET /api/models`. For example, the legacy block carries `attachment` and `modalities`: ```json "gpt-5.6-luna": { @@ -67,17 +69,21 @@ the metadata the proxy reports at `GET /api/models`: } ``` -Without those fields opencode assumes the model is text-only and refuses the paste on the -client side, so the image never reaches the proxy. That applies to text-only models too: when +V2 receives native `capabilities.input` and `capabilities.output` fields; known tool support +uses `capabilities.tools`. Legacy clients read the fields in the example above. When the catalog reports image input for a model the vision sidecar covers, opencode lets the attachment through so the sidecar can describe it before the upstream call. -What is written comes from the row's declared input modalities in `GET /api/models`. For a -discovered model the catalog adds `image` itself for the sidecar case; a custom row is written -from the modalities stored on it, so a custom entry that declares text only stays text-only -even when the sidecar would cover it. A row that declares none — an undeclared custom model, -for example — keeps the plain entry (`name`, plus `limit` when its context window is known), -and opencode treats it as text-only. +V2 requires a `tools` boolean whenever the native capabilities object exists. When tool support +is unknown, that object is omitted; known input modalities still reach V2 through the legacy +block's migration. This avoids inventing tool support or invalidating the provider's native settings. + +Unknown capabilities leave OpenCode's own fallback assumptions in place; they are not detected +provider facts. Explicit text-only metadata prevents image requests on the client side. + +Custom models inherit known catalog metadata unless an explicit override is stored. A custom +entry declaring text only stays text-only. Unknown capabilities are omitted rather than +invented; an explicit empty reasoning ladder does not declare the model incapable of reasoning. ## Your own config is never modified @@ -129,7 +135,9 @@ ocx export --client opencode --out ~/opencodex-opencode.json ::: Unlike the launcher's runtime block, a merged block is a static snapshot: it does not follow your -catalog. Re-run `ocx export` after you add a provider or change model visibility. +catalog. Re-run `ocx export` after you add a provider or change model visibility or metadata. +Alternatively, [enable the managed integration](/guides/integrations/): `ocx sync` safely +refreshes its already-owned blocks, leaving hand-edited or removed blocks untouched. Once merged, export the admission key before launching opencode — unless the proxy is on loopback, where none is needed: @@ -187,6 +195,9 @@ does not, the whole `limit` block is omitted and opencode keeps its own defaults Output limits use the model’s known maximum from catalog or generated metadata. Only unknown limits fall back to `32000`. The output limit is always clamped to the context window, including known limits below `32000`. +Known input limits are also carried and clamped to context; unknown input limits are omitted. +CLI export, dashboard export, managed refresh and the launcher share the effective metadata. + The `opencodex` provider block is regenerated on every launch, so per-model tweaks made inside it will not survive. Keep custom entries under a provider key of your own instead. diff --git a/docs-site/src/content/docs/guides/providers.md b/docs-site/src/content/docs/guides/providers.md index 18f52701c82..1d1384eecc5 100644 --- a/docs-site/src/content/docs/guides/providers.md +++ b/docs-site/src/content/docs/guides/providers.md @@ -210,6 +210,8 @@ ocx logout | `devin` | `devin` | `https://server.codeium.com` | Experimental unofficial Cognition/Devin bridge. Login first imports the credential the installed Devin CLI already holds (`devin auth login` writes a `devin-session-token` to its own `credentials.toml`); when none is present it opens Auth0 browser sign-in and exchanges the pasted token via Cognition's `RegisterUser` for a long-lived API key. `ocx login devin-cli` remains as a deprecated alias. Models are discovered per account with `GetCascadeModelConfigs`. Account quota comes from `GetUserStatus` under one eight-second request and body deadline: dated daily and weekly windows, plus the monthly prompt and flex credit pool for a credit-billed plan or an unknown strategy with both reset dates absent when a balance field is present. Negative used credits omit the monthly window; valid zero available credits mark it exhausted. A timed-out probe keeps the last good quota. Not shown in the dashboard preset by default. Chat and usage reporting are verified against a live account across three models. | | `github-copilot` | `openai-chat` | `https://api.githubcopilot.com` | Experimental. GitHub device flow + `copilot_internal` exchange (VS Code OAuth client). Requires an active Copilot subscription; not an official third-party API. | +Google Antigravity groups a model's complete `low` / `medium` / `high` variants into one entry with an effort selector. This includes Claude Opus and Sonnet 5.5 and future versions discovered during automatic refresh. Partial sets remain separate. Existing enabled/disabled choices carry into the grouped entry; genuinely new models still follow your New model policy. Saved suffix IDs continue to request their original tier. Grouped effort routing survives a proxy restart when model discovery is temporarily unavailable. A running Codex session may need to reload its model list after catalog refresh. Usage groups known Claude Sonnet and Opus 5.5 tier IDs under their base model, including historical tier records. Their displayed costs are derived Anthropic reference estimates, not Antigravity subscription charges. + Google Antigravity account and provider quota probes use fixed Google accounting endpoints, including the models fallback. They support transparent Fake-IP DNS for those destinations while retaining TLS verification, redirect rejection and private-address checks. A custom provider base URL changes model requests, not quota destinations; `NO_PROXY` continues to select the direct-route policy. ### Google tool-schema loss diagnostics @@ -602,9 +604,9 @@ OpenAI-compatible API gateway at [tokenlab.sh](https://tokenlab.sh/r/OPENCODEX), operated by TOKENLAB AI INC. Create a workspace [API key](https://tokenlab.sh/dashboard/api?tab=keys), then run `ocx provider add tokenlab` or select **TokenLab** in the dashboard's **Add provider** picker. -TokenLab maintains a step-by-step [OpenCodex integration guide](https://docs.tokenlab.sh/integrations/opencodex) -([한국어](https://docs.tokenlab.sh/ko/integrations/opencodex)) covering setup and per-model routing. -The preset uses [Chat Completions](https://docs.tokenlab.sh/quickstart) and discovers models at +TokenLab maintains a step-by-step [OpenCodex integration guide](https://tokenlab.sh/docs/en/integrations/opencodex) +([한국어](https://tokenlab.sh/docs/ko/integrations/opencodex)) covering setup and per-model routing. +The preset uses [Chat Completions](https://tokenlab.sh/docs/en/quickstart) and discovers models at `GET /v1/models?category=chat`, keeping only entries that declare `tool-use` capability. Image, video, audio, embedding and decision models are excluded from this chat preset. @@ -613,8 +615,8 @@ Each model then uses the request format TokenLab declares for it | Models | Codex (Responses clients) | Chat clients | Claude Code (Anthropic clients) | | --- | --- | --- | --- | -| `gpt-6-astra`, `gpt-6.1-sol`, `gpt-6-sol`, `gpt-6-luna`, `grok-4.7`, `deepseek-v4.1-flash`, `deepseek-v4-pro`, `kimi-k3`, `glm-5.3` | [Responses](https://docs.tokenlab.sh/api-reference/responses/create-response) | Chat Completions | Chat Completions | -| `claude-*` | [Messages](https://docs.tokenlab.sh/api-reference/messages/create-message) | Messages | Messages | +| `gpt-6-astra`, `gpt-6.1-sol`, `gpt-6-sol`, `gpt-6-luna`, `grok-4.7`, `deepseek-v4.1-flash`, `deepseek-v4-pro`, `kimi-k3`, `glm-5.3` | [Responses](https://tokenlab.sh/docs/en/api-reference/responses/create-response) | Chat Completions | Chat Completions | +| `claude-*` | [Messages](https://tokenlab.sh/docs/en/api-reference/messages/create-message) | Messages | Messages | | Every other model, including `gemini-3.8-flash` | Chat Completions | Chat Completions | Chat Completions | To keep a model on Chat Completions, add it to the provider's `modelAdapters`, for example @@ -622,7 +624,7 @@ To keep a model on Chat Completions, add it to the provider's `modelAdapters`, f provider points at `https://api.tokenlab.sh/v1`. OpenCodex sends no delivery-policy header, so your API key's own delivery policy decides how TokenLab serves each request. -The [model catalog](https://docs.tokenlab.sh/api-reference/models/list-models) is public without +The [model catalog](https://tokenlab.sh/docs/en/api-reference/models/list-models) is public without a key, but a supplied key is validated and scopes results to its model permissions and delivery policy. Use a valid key with a funded workspace for inference. `gpt-5.6-terra` is the seeded default; choose another discovered model if your key does not allow it. The provider and model diff --git a/docs-site/src/content/docs/guides/remote-link.md b/docs-site/src/content/docs/guides/remote-link.md index 9cb6bf7d023..b4d1e76beb1 100644 --- a/docs-site/src/content/docs/guides/remote-link.md +++ b/docs-site/src/content/docs/guides/remote-link.md @@ -11,7 +11,7 @@ A machine link connects an OpenCodex **Home** computer to a **Child** computer o - For a Child-initiated link, the Child can log in to Home with an OpenSSH key (password login is not supported). - OpenCodex 2.66.0 or later is installed on the Child computer, and on Home for a Child-initiated link. - Both computers run macOS or Linux. -- The dashboard that starts the link is opened on that computer itself (browser or desktop app, standalone install) or through a paired Hub session. +- The dashboard that adds a Child from Home is opened on Home itself or through a paired Hub session. Turning the current computer into a Child requires an operator-paired dashboard session; a credentialless local dashboard session cannot commit that routing change. Password SSH and Windows are outside the current flow. A link can be started from either side: from the Home, as described next, or from the Child, as described in [Connect this computer as a Child](#connect-this-computer-as-a-child). @@ -25,6 +25,17 @@ Password SSH and Windows are outside the current flow. A link can be started fro The dashboard does not ask you to enter a token. It probes the host first, and it cannot apply the link until you explicitly confirm the fingerprint. +The Add Child sheet explains that the Child uses this Home's providers over SSH and lists the prerequisites above. Host aliases come from `~/.ssh/config` on the Home running OpenCodex, not necessarily the computer displaying the browser. If discovery succeeds with no hosts, add a `Host` entry like this, then choose **Rescan hosts**. You can also enter an existing alias manually; selecting or entering an alias enables **Test connection**. The sheet links to this guide. + +```sshconfig +Host devbox + HostName devbox.example.com + User you + IdentityFile ~/.ssh/id_ed25519 +``` + +If discovery fails, the sheet shows **Could not load SSH hosts**, the available request reason and **Retry**, rather than claiming the host list is empty. A connection-test failure keeps its specific reason and sanitized SSH hint in the active sheet, not duplicated behind it. Check the reason, use the SSH diagnostic in Troubleshooting below when relevant, and retry. Rescanning keeps the entered alias but requires fresh fingerprint review before connecting. + ## Connect this computer as a Child On the computer that should use the Home's providers: @@ -38,7 +49,9 @@ Connecting restarts OpenCodex on this computer. Codex turns that are already run The Child waits for its configured port while the old process releases it. If a CLI-managed restart still fails, run `ocx start` on the Child and check `~/.opencodex/restart-handoff.log`. In the desktop app, the app starts and supervises the replacement automatically. -The **Child** role is available only while OpenCodex runs on its configured port, because the Child restarts on exactly that port. If the dashboard says OpenCodex is not running on its configured port, restart it there first. +If **Child** says to pair this machine first, open this computer's configured literal-loopback HTTP dashboard, for example `http://127.0.0.1:`. The local pairing form appears only when pairing is missing and the dashboard and API use the same loopback origin. Copy the form's `ocx gui pair --origin "http://127.0.0.1:"` command, run it in a terminal on this computer, and paste the one-use code into the form. Use the exact origin shown in the form; a provider API key or admin token is not a pairing code. Missing pairing is separate from a configured-port mismatch. + +The **Child** role also requires a standalone OpenCodex runtime running on its configured port, because the Child restarts on exactly that port. If the dashboard says OpenCodex is not running on its configured port, restart it there first. ## Link status diff --git a/docs-site/src/content/docs/guides/web-dashboard.md b/docs-site/src/content/docs/guides/web-dashboard.md index 3a4c01848ff..3adf5b7a12d 100644 --- a/docs-site/src/content/docs/guides/web-dashboard.md +++ b/docs-site/src/content/docs/guides/web-dashboard.md @@ -154,11 +154,20 @@ fallback until a valid sample is available. Speed uses output tokens per second full request duration: below 15, 15 to below 50, or at least 50. Unavailable speed values are excluded when a speed filter is active. Success means 2xx; errors mean 4xx or 5xx. +The request detail also shows **Decode rate (est.)**. When the proxy observed both ends, it is +output tokens over the generation window: from the first output item or block, reasoning included, +to the last output delta. Older rows without that window use the time after the first visible +token instead. Both are proxy-side observations, not the provider's internal token timing, so the +value is always an estimate, and a window under one second shows as unavailable. The end-to-end +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. +The language picker includes **Português** (Brazilian Portuguese, `pt-BR`). Portuguese browser languages select it automatically unless a supported language preference is already saved. + ### Linking to a section There is a single layout, so there is no layout switch to configure. Dashboard sections are @@ -172,6 +181,8 @@ For a custom usage interval, the server must confirm the exact requested start a If an older running proxy does not support those bounds, the dashboard and CLI reject its report; upgrade and restart that proxy before retrying. Resetting a manual model price affects only that model, preserving other rates saved independently. +The **Usage** summary and Models/Providers tables show end-to-end output throughput from usage history for the selected range and filters: summed output tokens divided by summed wall-clock seconds, not an average of individual rates. The sample count is measured attempts (or requests for legacy rows without attempts); samples lacking positive finite output tokens or duration are excluded. An em dash means no sample qualified or the server returned an unusable rate. This includes pre-decode waiting and is not estimated decode speed. + The **Usage** Models and Providers tables show the estimated priced portion for each row. Requests without a matching price or usable usage are counted as excluded beside that amount when the proxy reports pricing coverage fields. A row with only excluded requests shows an em dash with that count @@ -201,6 +212,11 @@ new or that every upstream measurement was refreshed. The **Models** switches show final Codex visibility: a routed model is on only when its provider allowlist includes it (or no allowlist is set) and it is not disabled. Turning a model on reconciles both filters atomically; **All on** clears the provider allowlist so newly discovered models are also on. +Switches respond immediately so you can keep changing models while saves run in the background in +click order. Saved feedback appears after the queue finishes and the list is reconciled with the +server. Failed saves restore the server's state when it can be read and show an error. Wait for that +feedback before leaving Models or changing servers: unsent queued changes are discarded on departure. + ### Managing models in a provider workspace In a provider’s **Models** tab, **Delete** removes the stored custom definition. An underlying diff --git a/docs-site/src/content/docs/ja/guides/claude-code.md b/docs-site/src/content/docs/ja/guides/claude-code.md index 609ff3c85c9..834d27eec40 100644 --- a/docs-site/src/content/docs/ja/guides/claude-code.md +++ b/docs-site/src/content/docs/ja/guides/claude-code.md @@ -601,3 +601,17 @@ Anthropic バックエンドを明示すると意図的に失敗後停止しま `config.json` の `claudeCode.stabilizePromptCache` を `true` にすると、変換ルートのシステム指示末尾にある対応済み Claude 通知を最後のユーザーメッセージへ移します。既定値は `false` です。このロール変更が適切なクライアントでのみ有効にしてください。コードフェンス内の例と一致しない本文は保持され、Anthropic のネイティブ転送は変わりません。メタデータがない場合のキャッシュキーは安定化した指示から計算されます。会話 ID の生成やキャッシュヒットの保証は行いません。 変換されたすべての Chat ルートで、タイムライン上のリマインダーは保留中のツール結果の後、会話内の元の位置を保ちます。これにより、新しいリマインダーを追加しても先頭のシステムプロンプトが書き換わらず、会話の途中に置かれた指示がそれより前のターンの前に移動することもありません。そのスロットが運ぶロールは別に決まります。プロバイダーが `foldDeveloperRoleToSystem: false` を記録していないかぎり、リマインダーは `system` として送られます。この記録は上流が `developer` ロールを受け付けることを表し、その場合は同じ位置のまま転送します。受け付けない上流は `400 role 'developer' is not allowed` を返してターンが始まらないため、記録のない宛先は畳む側になります。`stabilizePromptCache` の設定にかかわらず適用され、Anthropic のネイティブ転送は変わりません。キャッシュの再利用には、安定したセッション ID と上流キャッシュの利用可能性が引き続き必要です。過去の指示やツールの変更、会話の圧縮もキャッシュヒットに影響します。リマインダーの順序を保つだけで再利用が保証されるわけではありません。 + +### Forced Claude Code subagent model + +The Subagents page offers **Force all subagents onto one model**, off by default. Select an exposed roster-style id, such as `combo/tev-auto`, then enable the switch. The roster is offered first; unavailable saved roster entries cannot be force targets. + +`ocx agent subagents force combo/tev-auto` sets `claudeCode.subagentModelForce`; `ocx agent subagents force -` clears it. `ocx agent status` reports the setting. `GET /api/subagent-models` returns `force`, `forceAvailable`, and `forceStatus`; `PUT` accepts `{ "force": "combo/tev-auto" }` or `{ "force": null }` without changing the roster. Omitting `force` leaves it unchanged. Invalid or unexposed targets are rejected on write; stale targets are reported and skipped at launch. + +This takes effect on the **next routed `ocx claude` launch**, injecting `CLAUDE_CODE_SUBAGENT_MODEL` as an explicit proxy alias (with `[1m]` only for an authoritative million-token window; native Claude targets use a reversible native alias) and `CLAUDE_CODE_SUBAGENT_MODEL_FORCE=1`. Each nonempty shell-exported variable independently wins. Native launches inject neither variable; plain `claude` is not affected. No plugin files or `settings.json` are modified by this setting. + +Claude Code **2.1.257 or newer** is required for FORCE. Plugin and built-in agents (including Explore/Plan) and per-call model arguments are overridden. Forks and subagent skills with `model: inherit` keep the main conversation model. The main loop and Haiku/small-fast sidecars are unaffected. Existing roster files remain available. + +The dashboard warns about old or unknown CLI versions, unavailable targets, and either variable already present in `settings.json` → `env` (which overrides launch env). Detection is read-only and server-local: it cannot inspect another launch shell, another machine, or project-local settings. An unknown result is not proof of force support. + +Explicit gateway selectors on a generated agent request take precedence over its legacy `ocx-route` fallback, even if the saved force setting changes after launch. For shell or settings overrides of generated roster agents, use an explicit gateway alias; bare Claude ids retain the older-client fallback behavior. Native aliases restore their bare model before the existing credential and model-map checks. Connected launches validate force targets against a fresh authenticated gateway catalog; failed discovery skips automatic force injection, and cached context windows alone never prove availability. diff --git a/docs-site/src/content/docs/ja/guides/combos.md b/docs-site/src/content/docs/ja/guides/combos.md index 3d4d0c2e378..1a03b3e6a07 100644 --- a/docs-site/src/content/docs/ja/guides/combos.md +++ b/docs-site/src/content/docs/ja/guides/combos.md @@ -125,6 +125,7 @@ ocx combo set balanced \ |モデルのサポート終了、retired、deprecated、sunset、decommissioned、または利用不可を明示する HTTP 410 |そのターゲットをクールダウンし、次へ進みます。無関係な 410 はターミナル エラーのままです。 | |機密認証、サブスクリプション、クォータ、レート制限、過負荷、またはアップストリーム サーバー エラー |ステータスだけでは物足りない場合でもターゲットを冷やしてホップさせましょう。 | |クライアントのキャンセル (499)、`origin_rejected`、サイバー ポリシーの拒否、コンテキスト オーバーフロー、またはその他の無効なリクエスト |停止してエラーを返します。別のターゲットではリクエストは有効になりません。 | +| ChatGPT アカウントで Codex を使う際に、そのモデルをサポートしないことを示す正確な HTTP 400 の拒否 | 出力を確定する前に、次の設定済みターゲットを試します。拒否文全体が `detail`、`error.message`、または本文そのものにある場合に限ります。`detail` と `error` が競合する応答は終了扱いです。この拒否だけでアカウントを再認証待ちにしたり、プロバイダーの全モデルをクールダウンしたりしません。 | |`user` の明示的な拒否、`reasoning.effort`/`reasoning_effort` の非対応値、またはモデル固有の画像入力拒否(`param: input`)を示す構造化 HTTP 400 |出力開始前に次の適格なターゲットへ進み、クールダウンを記録しません。任意パラメーターの互換性を参照してください。 | |インプロセスアダプター(`runTurn`)が実行する Responses ターンで、現在のリクエストが宣言していない最初のツール呼び出し(出力やリプレイ不可の副作用より前) |ターゲットをクールダウンし、同じツールカタログで次へ進みます。可視出力やリプレイ不可の副作用の後は拒否が確定します。Chat Completions と Anthropic Messages のリクエストは変わりません。 | |その他の未分類のエラー |停止してエラーを返します。 | diff --git a/docs-site/src/content/docs/ja/guides/integrations.md b/docs-site/src/content/docs/ja/guides/integrations.md index 15e2f8ba560..5012d52484e 100644 --- a/docs-site/src/content/docs/ja/guides/integrations.md +++ b/docs-site/src/content/docs/ja/guides/integrations.md @@ -39,7 +39,7 @@ modelProfile: 対応する推論負荷の段階を持つ GJC モデルは、`reasoning: true`、`thinking.levels`、`compat.supportsReasoningEffort` を出力し、GJC で負荷を選べるようにします。ネイティブ Codex モデルでは、カタログに段階がなくても標準の段階を出力します。段階が不明なモデルではこれらの項目を省略します。`none` は負荷を送信せず、`ultra` は送信時に `max` に変換されるため、選択肢には含めません。モデルの選択肢を更新するには統合を更新してください。 -管理対象の OpenCode 統合は、`provider.opencodex`(opencode V1)と `providers.opencodex`(opencode V2)の 2 つの部分を所有します。モデルごとの推論負荷の選択肢は V2 ブロックだけに含まれるため、両方を書き込んで同期します。両者は同じプロバイダー ID とモデル ID を指定し、opencode V2 は 1 つのプロバイダー項目に統合します。Apply、Refresh、Disable、Restore は両方に作用し、他のプロバイダー、エージェント、キー割り当て、MCP 項目には触れません。 +管理対象の OpenCode 統合は、`provider.opencodex`(opencode V1)と `providers.opencodex`(opencode V2)の 2 つの部分を所有します。両ブロックとも、宣言されたモデルごとの推論負荷の選択肢と既定値を同じ内容で持ちます。レガシーブロックではモデルの options の既定値とバリアントマップで、V2 ブロックではモデルの settings とネイティブなバリアント配列で表現され、両方が書き込まれて同期します。両者は同じプロバイダー ID とモデル ID を指定し、opencode V2 は 1 つのプロバイダー項目に統合します。Apply、Refresh、Disable、Restore は両方に作用し、他のプロバイダー、エージェント、キー割り当て、MCP 項目には触れません。これらのモデル定義は、Kilo のエクスポートや `ocx opencode` ランチャーと同じ実効的な正規モデルメタデータから作られます。明示的なカスタム上書きは常に優先され、空にクリアされた段階はそのまま維持され、段階も既定値も捏造されません。信頼できるコンテキスト上限が携帯され、その横に出力上限が並びます。出力は既知の値(明示的またはカタログメタデータ)が優先され、未知の場合はスキーマ必須の 32000 フォールバック(コンテキストにクランプ)が使われ、信頼できるコンテキストがなければ limit ブロック自体が省略されます。機能、負荷の段階、既定値、任意の入力上限は既知の場合にのみ書き込まれます。V2 ブロックのネイティブな capabilities オブジェクトには既知の tools 値が必要なため、ツール対応が不明な場合は一部分だけを書くのではなくオブジェクトごと省略され、レガシー側のモダリティ宣言はそのまま残ります。段階を捏造しない代わりに制御だけを書きます。V2 ブロックは常に明示的なバリアント配列を持ち、宣言がない場合は空になり、省略すると OpenCode が low/medium/high を合成するためです。固定された推論は既知だが調整可能な段階がないモデルには、選択可能な負荷ではなく無効化専用の抑止が書かれます。レガシー OpenCode ブロックも Kilo も、クライアントが本来生成する各段階 ID をすべて無効化します。どちらも選択可能な負荷は追加しません。選択可能なバリアント(モデル自身の段階が `none` を宣言している場合の `none` を含む)は、既知の場合に書き込まれる既定の推論負荷を上書きできます。クライアントが何を選んでも、プロキシの上流向けに固定されたポリシーは引き続き実際のリクエストに適用されます。 管理対象の DSH 統合には **DSH 0.1.0-rc.6** 以降が必要です。OpenCodex が所有するのは `llm-pi-ai.providers.opencodex` のみです。Apply と Refresh はその部分を置き換え、Disable はその部分のみを削除し、Restore は記録されたスナップショットを戻します。DSH はプロバイダー変更をホットリロードします。これらの操作はユーザーのデフォルトモデルやネイティブの `deepseek-official` プロバイダーを変更しません。管理対象の DSH 統合は現在ループバック専用で、実際の認証情報は書き込みません。 @@ -115,7 +115,7 @@ TOML の日付と時刻も、管理対象の書き換えでは拒否されます **Kimi Code は環境変数への参照を保持できません。** そのため設定にはキーではなく `opencodex-loopback` プレースホルダーが入ります。どのクライアント設定にも実際の認証情報は書き込みません。 -**`ocx opencode` ではランチャーのプロバイダーブロックが優先されます。** このランチャーは `OPENCODE_CONFIG_CONTENT` を通じて `provider.opencodex` と `providers.opencodex` を注入し、ディスク上の同じ項目より優先します。他の opencode 設定は通常どおり適用されます。このスイッチが重要になるのは `opencode` を直接起動する場合です。 +**`ocx opencode` ではランチャーのプロバイダーブロックが優先されます。** このランチャーは `OPENCODE_CONFIG_CONTENT` を通じて `provider.opencodex` と `providers.opencodex` を注入し、ディスク上の同じ項目より優先します。他の opencode 設定は通常どおり適用されます。これらのブロックはエクスポートと同じ実効的な正規モデルメタデータから構築されるため、ランチャーと管理対象の統合は同じモデル、上限、推論の選択肢を記述します。このスイッチが重要になるのは `opencode` を直接起動する場合です。 ## ターミナルから操作する @@ -144,9 +144,9 @@ ocx integration client enable --client mcode ocx mcode ``` -接続後、`ocx sync` と `POST /api/sync` は、所有する MCode、Pi、Aside、Raycast、omo のカタログを現在のモデル選択、コンテキストウィンドウ、推論負荷の段階で更新します。プロキシ起動時には所有する Raycast カタログを更新します。モデルの表示設定、プロバイダーの選択、プリセットの変更でも、接続済みの Pi、Aside、Raycast、omo カタログが更新されます。存在しないブロック、他者が編集したブロック、安全でないブロック、および手動で削除した以前の所有ブロックには触れません。 +接続後、`ocx sync` と `POST /api/sync` は、所有する MCode、Pi、Aside、Raycast、omo、OpenCode、Kilo のカタログを現在のモデル選択、コンテキストウィンドウ、推論負荷の段階で更新します。プロキシ起動時には所有する Raycast カタログを更新します。モデルの表示設定、プロバイダーの選択、プリセットの変更でも、接続済みの Pi、Aside、Raycast、omo、OpenCode、Kilo カタログが更新されます。存在しないブロック、他者が編集したブロック、安全でないブロック、および手動で削除した以前の所有ブロックには触れません。 -有効な Aside プロファイルは、通常の「所有済みのみ更新」の例外です。アカウントディレクトリが存在し、まだ所有ブロックがなく、その場所が空であれば、同期時に最初のブロックを作れます。以前の Aside 接続があれば、登録済みの全プロファイルでこの動作がデフォルトで有効になります。同期は存在しないアカウントディレクトリを作らず、手動のブロックも置き換えません。拒否または重複する更新は、クライアントごとに別々に報告されます。更新されたファイルを読み込むには、新しい Pi セッションを開始するか、Aside を完全に終了して開き直してください。Aside の更新には[対応する稼働中のプロキシ](#aside-プロファイルの管理)が必要です。 +有効な Aside プロファイルは、通常の「所有済みのみ更新」の例外です。アカウントディレクトリが存在し、まだ所有ブロックがなく、その場所が空であれば、同期時に最初のブロックを作れます。以前の Aside 接続があれば、登録済みの全プロファイルでこの動作がデフォルトで有効になります。同期は存在しないアカウントディレクトリを作らず、手動のブロックも置き換えません。拒否または重複する更新は、クライアントごとに別々に報告されます。更新されたファイルを読み込むには、新しい Pi、OpenCode、Kilo セッションを開始するか、Aside を完全に終了して開き直してください。Aside の更新には[対応する稼働中のプロキシ](#aside-プロファイルの管理)が必要です。 Models に **“Model selection saved”** とクライアント更新の警告が一緒に表示された場合、モデルの選択自体はすでに保存されていますが、1 件以上のクライアントファイルを更新できていません。警告には該当するクライアントと、必要に応じて Aside プロファイル、拒否理由が示されます。新しいセッションを始める前に **Integrations** で対象を確認してください。報告された問題を解消して `ocx sync` を再試行します。重複する操作は先に完了させてください。警告にバックアップパスがある、または復旧が未完了と表示される場合は、再試行前にその状態を調べてください。モデル選択の保存成功だけでは、クライアントファイルの復旧は確認できません。 @@ -224,9 +224,9 @@ Kilo CLI、VS Code、JetBrains は同じグローバル設定を共有します Kilo はこれらのグローバルファイルをすべてマージします。別の候補も `provider.opencodex` を定義する場合、状態に競合ファイルが表示され、適用と置換は拒否されます。有効化する前に、そのファイルから `provider.opencodex` を削除してください。所有済みファイルの無効化は競合があっても実行できます。読み取れない候補や安全に扱えない候補も書き込みを妨げます。 -管理対象は OpenCode V1 形式の `provider.opencodex`(`npm`、`options`、`models`)だけです。OpenCode V2 の `providers` は出力しません。`$schema`、`model`、`enabled_providers`、MCP などのキーはユーザーが管理します。適用後、Kilo で `opencodex/` を選択してください。 +管理対象は OpenCode V1 形式の `provider.opencodex`(`npm`、`options`、`models`)だけです。OpenCode V2 の `providers` は出力しません。代わりに、モデルごとの推論負荷の選択肢は、プロバイダーブロック内のバリアントマップとして、OpenCode エクスポートと同じ実効的な正規メタデータから書き込まれます。`$schema`、`model`、`enabled_providers`、MCP などのキーはユーザーが管理します。適用後、Kilo で `opencodex/` を選択してください。 -ループバックでは `options.apiKey` に `{env:OPENCODEX_KILO_API_KEY}` を使います。ループバック以外へのバインドでは認証を `options.headers["x-opencodex-api-key"]` に移し、実際のキーは保存しません。適用時はグローバルファイル全体を整形済み JSON として書き直すため、他のキーのコメントと末尾カンマは保持されません。Kilo は自動カタログ更新の対象外です。ルーティング対象のモデル選択を変更したら、明示的に更新してください。 +ループバックでは `options.apiKey` に `{env:OPENCODEX_KILO_API_KEY}` を使います。ループバック以外へのバインドでは認証を `options.headers["x-opencodex-api-key"]` に移し、実際のキーは保存しません。適用時はグローバルファイル全体を整形済み JSON として書き直すため、他のキーのコメントと末尾カンマは保持されません。Kilo は自動カタログ更新の対象です。`ocx sync`、`POST /api/sync`、および表示設定・プロバイダー・プリセットの変更は、他のクライアントと同じ所有ブロックのみを対象にする安全な規則で所有済みの Kilo ブロックを更新し、新しい Kilo セッションが更新後のファイルを読み込みます。 ```bash ocx integration client enable --client kilo diff --git a/docs-site/src/content/docs/ja/guides/remote-link.md b/docs-site/src/content/docs/ja/guides/remote-link.md index d8878f2fbd8..770e9a0ec5c 100644 --- a/docs-site/src/content/docs/ja/guides/remote-link.md +++ b/docs-site/src/content/docs/ja/guides/remote-link.md @@ -11,7 +11,7 @@ description: SSH で OpenCodex の Home コンピューターと Child コンピ - Child から開始するリンクでは、Child から Home に OpenSSH キーでログインできる必要があります(パスワードログインには対応していません)。 - Child に OpenCodex 2.66.0 以降がインストールされていること(Child から開始するリンクでは Home にも)。 - 両方のコンピューターが macOS または Linux であること。 -- リンクを開始するダッシュボードは、そのコンピューター上で直接開いたもの(スタンドアロン環境のブラウザーまたはデスクトップアプリ)か、ペアリング済みの Hub セッションであること。 +- Home から Child を追加するダッシュボードは Home 上で開くか、ペアリング済みの Hub セッションを使用します。現在のコンピューターを Child にするには、オペレーターがペアリングしたダッシュボードセッションが必要です。認証情報なしのローカルセッションでは、このルーティング変更を確定できません。 パスワード SSH と Windows は現在のフローに含まれません。リンクはどちら側からでも開始できます。次の手順のように Home から開始するか、後述の「このコンピューターを Child として接続する」のように Child から開始します。 @@ -25,6 +25,17 @@ description: SSH で OpenCodex の Home コンピューターと Child コンピ ダッシュボードはトークンの入力を求めません。先にホストをプローブし、フィンガープリントを明示的に確認するまでリンクを適用しません。 +Child 追加画面には、Child が SSH 経由でこの Home のプロバイダーを使用することと、上記の前提条件が表示されます。別名は OpenCodex を実行する Home の `~/.ssh/config` から取得し、ブラウザーを表示するコンピューターとは限りません。検索が成功してもホストがない場合は、次のような `Host` エントリを追加して再検索してください。既存の別名の手入力も可能です。別名を選択または入力すると接続テストが有効になります。このガイドへのリンクもあります。 + +```sshconfig +Host devbox + HostName devbox.example.com + User you + IdentityFile ~/.ssh/id_ed25519 +``` + +検索に失敗した場合、空の一覧と表示せず、読み込み失敗、取得できた理由、再試行ボタンを表示します。接続テストの失敗理由とサニタイズ済み SSH ヒントは開いている画面だけに表示され、背後には重複しません。理由と下記の SSH 診断を確認して再試行してください。再検索では入力した別名を保持しますが、接続前にフィンガープリントの再確認が必要です。 + ## このコンピューターを Child として接続する Home のプロバイダーを使うコンピューターで次の操作を行います。 @@ -36,7 +47,9 @@ Home のプロバイダーを使うコンピューターで次の操作を行い 接続すると、このコンピューターの OpenCodex が再起動します。すでに実行中の Codex ターンは先に完了し、再起動中の最大 1 分間は新しいリクエストが失敗することがあります。その後ダッシュボードは自動的に再読み込みされ、Child のリンクを表示します。Codex はこのコンピューターの `http://127.0.0.1:/v1` を使い続け、トークンや環境変数の設定は不要です。ローカルの OpenCodex が各リクエストを Home に中継し、Home が自身のプロバイダーとアカウントで応答します。 -**Child** の役割は、OpenCodex が設定されたポートで動作している間だけ選択できます。Child はまさにそのポートで再起動するためです。設定されたポートで動作していないとダッシュボードに表示された場合は、先にそのポートで OpenCodex を再起動してください。 +**Child** にこのコンピューターを先にペアリングするよう表示された場合は、このコンピューターに設定された HTTP ループバック IP アドレスのダッシュボード(例: `http://127.0.0.1:`)を開いてください。ローカルのペアリングフォームは、ペアリングが必要で、ダッシュボードと API が同じループバックオリジンを使う場合にのみ表示されます。フォームの `ocx gui pair --origin "http://127.0.0.1:"` コマンドをコピーしてこのコンピューターのターミナルで実行し、使い捨てコードをフォームに貼り付けてください。フォームに表示された正確なオリジンを使ってください。プロバイダーの API キーや管理者トークンはペアリングコードではありません。ペアリングの不足と設定されたポートの不一致は別の原因です。 + +**Child** の役割には、OpenCodex がスタンドアロンモードで設定されたポートで動作していることも必要です。Child はまさにそのポートで再起動するためです。設定されたポートで動作していないとダッシュボードに表示された場合は、先にそのポートで OpenCodex を再起動してください。 ## リンクの状態 diff --git a/docs-site/src/content/docs/ja/reference/cli/agents.md b/docs-site/src/content/docs/ja/reference/cli/agents.md index bf7b59fc1b3..aca561b5a47 100644 --- a/docs-site/src/content/docs/ja/reference/cli/agents.md +++ b/docs-site/src/content/docs/ja/reference/cli/agents.md @@ -229,3 +229,15 @@ ocx system codex-cli-update attest --candidate --npm-prefix ...` 検証された OpenCodex 設定を検査し、安全に変更します。 `show` および `get` はシークレットをマスクします。インポートは書き込む前に検証され、`--yes` が必要です。 + +### Forced Claude Code subagent model + +The Subagents page offers **Force all subagents onto one model**, off by default. Select an exposed roster-style id, such as `combo/tev-auto`, then enable the switch. The roster is offered first; unavailable saved roster entries cannot be force targets. + +`ocx agent subagents force combo/tev-auto` sets `claudeCode.subagentModelForce`; `ocx agent subagents force -` clears it. `ocx agent status` reports the setting. `GET /api/subagent-models` returns `force`, `forceAvailable`, and `forceStatus`; `PUT` accepts `{ "force": "combo/tev-auto" }` or `{ "force": null }` without changing the roster. Omitting `force` leaves it unchanged. Invalid or unexposed targets are rejected on write; stale targets are reported and skipped at launch. + +This takes effect on the **next routed `ocx claude` launch**, injecting `CLAUDE_CODE_SUBAGENT_MODEL` as an explicit proxy alias (with `[1m]` only for an authoritative million-token window; native Claude targets use a reversible native alias) and `CLAUDE_CODE_SUBAGENT_MODEL_FORCE=1`. Each nonempty shell-exported variable independently wins. Native launches inject neither variable; plain `claude` is not affected. No plugin files or `settings.json` are modified by this setting. + +Claude Code **2.1.257 or newer** is required for FORCE. Plugin and built-in agents (including Explore/Plan) and per-call model arguments are overridden. Forks and subagent skills with `model: inherit` keep the main conversation model. The main loop and Haiku/small-fast sidecars are unaffected. Existing roster files remain available. + +The dashboard warns about old or unknown CLI versions, unavailable targets, and either variable already present in `settings.json` → `env` (which overrides launch env). Detection is read-only and server-local: it cannot inspect another launch shell, another machine, or project-local settings. An unknown result is not proof of force support. diff --git a/docs-site/src/content/docs/ja/reference/configuration/server.md b/docs-site/src/content/docs/ja/reference/configuration/server.md index c11d7af21c0..227ded775dd 100644 --- a/docs-site/src/content/docs/ja/reference/configuration/server.md +++ b/docs-site/src/content/docs/ja/reference/configuration/server.md @@ -187,3 +187,19 @@ Anthropic OAuth サイドカーは、opencodex の既存のクロード コー メイン Codex アカウント行の `quotaRefresh` はクォータ取得の診断情報であり、残量やモデルへのアクセス権を示すものではありません。キャッシュ利用時や取得を行わない場合は省略されることがあります。取得には操作中のシェルではなく、実行中のプロキシサービスの環境が使われます。`proxy` 未設定では既存の環境を維持し、`"auto"` は起動時の Windows または macOS の静的 HTTP/HTTPS 設定を読みます。macOS では継承したプロキシがある場合、読み取りを行いません。macOS では有効な `*.` を `.` に変換します。`*.local` は `foo.local` と基底名 `local` を直接接続にしますが、`xlocal` は対象外です。`169.254/16`、`169.254.0.0/16`、`fe80::/10` は診断を出して省略し、リンクローカル IP アドレスはプロキシを使います。IP アドレスと `*` は受け入れますが、その他の CIDR、glob、単純ホスト名の例外では環境を変更せず検出を中止します。PAC/WPAD、SOCKS のみの設定、実行中の変更は自動反映されません。TUN での成功だけでは HTTP プロキシ経路の正常性は確認できません。[コマンドと状態の説明(英語)](/reference/configuration/server/#codex-quota-network-diagnostics)を参照してください。 `dropCodexSafetyBuffering`: プロバイダーの安全性の適用と拒否応答は変更しません。native `codex.response.metadata.headers` WebSocket メタデータと `/responses/compact` は対象外です。 + +### Forced Claude Code subagent model + +The Subagents page offers **Force all subagents onto one model**, off by default. Select an exposed roster-style id, such as `combo/tev-auto`, then enable the switch. The roster is offered first; unavailable saved roster entries cannot be force targets. + +`ocx agent subagents force combo/tev-auto` sets `claudeCode.subagentModelForce`; `ocx agent subagents force -` clears it. `ocx agent status` reports the setting. `GET /api/subagent-models` returns `force`, `forceAvailable`, and `forceStatus`; `PUT` accepts `{ "force": "combo/tev-auto" }` or `{ "force": null }` without changing the roster. Omitting `force` leaves it unchanged. Invalid or unexposed targets are rejected on write; stale targets are reported and skipped at launch. + +This takes effect on the **next routed `ocx claude` launch**, injecting `CLAUDE_CODE_SUBAGENT_MODEL` as an explicit proxy alias (with `[1m]` only for an authoritative million-token window; native Claude targets use a reversible native alias) and `CLAUDE_CODE_SUBAGENT_MODEL_FORCE=1`. Each nonempty shell-exported variable independently wins. Native launches inject neither variable; plain `claude` is not affected. No plugin files or `settings.json` are modified by this setting. + +Claude Code **2.1.257 or newer** is required for FORCE. Plugin and built-in agents (including Explore/Plan) and per-call model arguments are overridden. Forks and subagent skills with `model: inherit` keep the main conversation model. The main loop and Haiku/small-fast sidecars are unaffected. Existing roster files remain available. + +The dashboard warns about old or unknown CLI versions, unavailable targets, and either variable already present in `settings.json` → `env` (which overrides launch env). Detection is read-only and server-local: it cannot inspect another launch shell, another machine, or project-local settings. An unknown result is not proof of force support. + +## トークン予約と上限 + +`spend.root.maxTokens`、`spend.identity.maxTokens`、`spend.pool.maxTokens` のいずれかがリクエストに適用される場合、追跡容量の不足などでトークン予約を記録できなければ送信を拒否します。適用される上限がないリクエストは観測のみを続けます。 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 cfdb48f8e77..3d8018ae3dc 100644 --- a/docs-site/src/content/docs/ja/reference/management-api.md +++ b/docs-site/src/content/docs/ja/reference/management-api.md @@ -364,3 +364,15 @@ Anthropic OAuth のみ。`{ provider: "anthropic", accountId, threshold }`: 整 DTO は `autoSwitchThresholdOverride`(整数/null)、`autoSwitchThreshold`(プール既定値)、`effectiveAutoSwitchThreshold` を含みます。0 は使用量による切り替えのみ無効にし、一時停止と 429 復旧は維持します。 HTTP: 400 invalid/unsupported; 404 missing account; `oauth_mutation_busy` on lock contention. + +### Forced Claude Code subagent model + +The Subagents page offers **Force all subagents onto one model**, off by default. Select an exposed roster-style id, such as `combo/tev-auto`, then enable the switch. The roster is offered first; unavailable saved roster entries cannot be force targets. + +`ocx agent subagents force combo/tev-auto` sets `claudeCode.subagentModelForce`; `ocx agent subagents force -` clears it. `ocx agent status` reports the setting. `GET /api/subagent-models` returns `force`, `forceAvailable`, and `forceStatus`; `PUT` accepts `{ "force": "combo/tev-auto" }` or `{ "force": null }` without changing the roster. Omitting `force` leaves it unchanged. Invalid or unexposed targets are rejected on write; stale targets are reported and skipped at launch. + +This takes effect on the **next routed `ocx claude` launch**, injecting `CLAUDE_CODE_SUBAGENT_MODEL` as an explicit proxy alias (with `[1m]` only for an authoritative million-token window; native Claude targets use a reversible native alias) and `CLAUDE_CODE_SUBAGENT_MODEL_FORCE=1`. Each nonempty shell-exported variable independently wins. Native launches inject neither variable; plain `claude` is not affected. No plugin files or `settings.json` are modified by this setting. + +Claude Code **2.1.257 or newer** is required for FORCE. Plugin and built-in agents (including Explore/Plan) and per-call model arguments are overridden. Forks and subagent skills with `model: inherit` keep the main conversation model. The main loop and Haiku/small-fast sidecars are unaffected. Existing roster files remain available. + +The dashboard warns about old or unknown CLI versions, unavailable targets, and either variable already present in `settings.json` → `env` (which overrides launch env). Detection is read-only and server-local: it cannot inspect another launch shell, another machine, or project-local settings. An unknown result is not proof of force support. diff --git a/docs-site/src/content/docs/ko/guides/claude-code.md b/docs-site/src/content/docs/ko/guides/claude-code.md index c33e412dc3b..d79d7940d72 100644 --- a/docs-site/src/content/docs/ko/guides/claude-code.md +++ b/docs-site/src/content/docs/ko/guides/claude-code.md @@ -386,7 +386,8 @@ Claude 페이지에서 압축 값을 조절할 수 있어요. **경고:** 모델 사용자가 만든 에이전트는 건드리지 않아요. - 파일마다 원자적으로 동기화해요(write + rename). - `enabled: false` 또는 `injectAgents: false`를 설정하면 소유권이 확인된 정의를 모두 정리해요. -- GUI PUT과 로스터 변경은 즉시 다시 동기화하고, launcher/system-env는 실행할 때 동기화해요. +- CLI 1P 설정 저장이나 Desktop 1P 설정 저장이 성공하면 에이전트 등록도 시도해요. 같은 설정을 다시 저장하면 누락된 정의를 복구할 수 있어요. 연결이 거부되거나 롤백되면 등록하지 않아요. 1P만 끄더라도 자동 등록은 유지해요. +- GUI PUT과 로스터 변경은 즉시 다시 동기화하고, launcher/system-env는 실행할 때 동기화해요. 등록한 에이전트를 쓰려면 Claude 세션을 새로 시작하세요. 디스패치 예시: `subagent_type: "ocx-gpt-5-6-sol"`. 1M을 지원하는 대상에는 `[1m]`이 자동으로 붙어요. @@ -663,3 +664,17 @@ Anthropic 백엔드를 명시하면 의도적으로 실패 후 중단해요. `config.json`에서 `claudeCode.stabilizePromptCache`를 `true`로 설정하면 번역 경로의 시스템 지시 끝에 붙은 지원 대상 Claude 알림을 마지막 사용자 메시지로 옮깁니다. 기본값은 `false`입니다. 사용하는 클라이언트에서 이 역할 변경을 허용할 때만 켜세요. 코드 펜스 안의 예제와 일치하지 않는 원문은 보존하며, Anthropic 원본 전달 경로는 바꾸지 않습니다. 메타데이터가 없는 요청의 캐시 키는 정리된 지시문을 기준으로 계산합니다. 대화 식별자를 만들거나 상위 서비스의 캐시 적중을 보장하는 기능은 아닙니다. 변환된 모든 Chat 경로에서 타임라인 알림은 대기 중인 도구 결과 뒤, 대화 안의 원래 위치를 그대로 유지합니다. 덕분에 새 알림을 추가해도 맨 앞의 시스템 프롬프트를 다시 쓰지 않고, 대화 중간의 지시가 그 지시보다 앞선 턴으로 끌려가지도 않습니다. 그 자리가 어떤 역할을 싣는지는 따로 정합니다. 공급자가 `foldDeveloperRoleToSystem: false`를 기록하지 않는 한 알림은 `system`으로 보내며, 이 기록은 상위 서비스가 `developer` 역할을 받아들인다는 뜻이라 같은 위치에서 그대로 전달합니다. 받아들이지 않는 상위 서비스는 `400 role 'developer' is not allowed`로 응답해 턴이 시작조차 못 하므로, 기록이 없는 목적지는 접는 쪽을 씁니다. `stabilizePromptCache` 설정과 관계없이 적용되며 Anthropic 네이티브 전달은 기존 동작을 유지합니다. 캐시 재사용에는 안정적인 세션 식별자와 사용 가능한 상위 서비스 캐시가 여전히 필요합니다. 이전 지시나 도구의 변경, 대화 압축도 캐시 적중에 영향을 줄 수 있으며, 알림 순서를 유지하는 것만으로 재사용을 보장하지는 않습니다. + +### Forced Claude Code subagent model + +The Subagents page offers **Force all subagents onto one model**, off by default. Select an exposed roster-style id, such as `combo/tev-auto`, then enable the switch. The roster is offered first; unavailable saved roster entries cannot be force targets. + +`ocx agent subagents force combo/tev-auto` sets `claudeCode.subagentModelForce`; `ocx agent subagents force -` clears it. `ocx agent status` reports the setting. `GET /api/subagent-models` returns `force`, `forceAvailable`, and `forceStatus`; `PUT` accepts `{ "force": "combo/tev-auto" }` or `{ "force": null }` without changing the roster. Omitting `force` leaves it unchanged. Invalid or unexposed targets are rejected on write; stale targets are reported and skipped at launch. + +This takes effect on the **next routed `ocx claude` launch**, injecting `CLAUDE_CODE_SUBAGENT_MODEL` as an explicit proxy alias (with `[1m]` only for an authoritative million-token window; native Claude targets use a reversible native alias) and `CLAUDE_CODE_SUBAGENT_MODEL_FORCE=1`. Each nonempty shell-exported variable independently wins. Native launches inject neither variable; plain `claude` is not affected. No plugin files or `settings.json` are modified by this setting. + +Claude Code **2.1.257 or newer** is required for FORCE. Plugin and built-in agents (including Explore/Plan) and per-call model arguments are overridden. Forks and subagent skills with `model: inherit` keep the main conversation model. The main loop and Haiku/small-fast sidecars are unaffected. Existing roster files remain available. + +The dashboard warns about old or unknown CLI versions, unavailable targets, and either variable already present in `settings.json` → `env` (which overrides launch env). Detection is read-only and server-local: it cannot inspect another launch shell, another machine, or project-local settings. An unknown result is not proof of force support. + +Explicit gateway selectors on a generated agent request take precedence over its legacy `ocx-route` fallback, even if the saved force setting changes after launch. For shell or settings overrides of generated roster agents, use an explicit gateway alias; bare Claude ids retain the older-client fallback behavior. Native aliases restore their bare model before the existing credential and model-map checks. Connected launches validate force targets against a fresh authenticated gateway catalog; failed discovery skips automatic force injection, and cached context windows alone never prove availability. diff --git a/docs-site/src/content/docs/ko/guides/combos.md b/docs-site/src/content/docs/ko/guides/combos.md index 44d790868a4..c9ccf3757b3 100644 --- a/docs-site/src/content/docs/ko/guides/combos.md +++ b/docs-site/src/content/docs/ko/guides/combos.md @@ -131,6 +131,7 @@ ocx combo set balanced \ | 모델 수명 종료, retired, deprecated, sunset, decommissioned, 또는 더 이상 사용할 수 없다는 신호가 명시된 HTTP 410 | 해당 대상만 쿨다운으로 보내고 다음 대상으로 넘어갑니다. 관련 없는 410은 종결 오류로 유지합니다. | | 인증, 구독, 쿼터, 속도 제한, 과부하, 또는 상위 서버 오류로 분류됨 | 상태 코드만으로는 충분하지 않더라도 대상을 쿨다운으로 보내고 넘어갑니다. | | 클라이언트 취소(499), `origin_rejected`, cyber-policy refusal, context overflow, 또는 기타 invalid request | 멈추고 오류를 반환합니다. 다른 대상을 써도 요청이 유효해지지 않기 때문입니다. | +| ChatGPT 계정으로 Codex를 사용할 때 해당 모델을 지원하지 않는다는 정확한 HTTP 400 거부 | 출력이 시작되기 전에 다음 설정 대상으로 넘어갑니다. 거부 문장 전체가 `detail`, `error.message` 또는 일반 텍스트에 있어야 하며, `detail`과 `error`가 충돌하면 중단합니다. 이 사유만으로 계정을 재인증 상태로 바꾸거나 공급자의 모든 모델에 쿨다운을 걸지는 않습니다. | | `user`를 명시적으로 거부하거나, `reasoning.effort`/`reasoning_effort`의 지원되지 않는 값 또는 모델별 이미지 입력 거부(`param: input`)를 나타내는 구조화된 HTTP 400 | 출력 시작 전에 쿨다운 기록 없이 다음 적격 대상으로 넘어갑니다. 선택적 매개변수 호환성을 참조하세요. | | 인프로세스 어댑터(`runTurn`)가 실행하는 Responses 턴에서 현재 요청이 선언하지 않은 첫 도구 호출(출력이나 재전송 불가 부작용 이전) | 대상을 쿨다운하고 같은 도구 카탈로그로 다음 대상으로 넘어갑니다. 출력이 보였거나 재전송 불가 부작용이 생긴 뒤에는 거부가 그대로 확정됩니다. Chat Completions와 Anthropic Messages 요청은 바뀌지 않습니다. | | 그 밖의 분류되지 않은 오류 | 멈추고 오류를 반환합니다. | diff --git a/docs-site/src/content/docs/ko/guides/integrations.md b/docs-site/src/content/docs/ko/guides/integrations.md index e39df9ea9da..aa3e9b8d4d4 100644 --- a/docs-site/src/content/docs/ko/guides/integrations.md +++ b/docs-site/src/content/docs/ko/guides/integrations.md @@ -39,7 +39,7 @@ modelProfile: 지원되는 추론 강도 단계가 있는 GJC 모델은 `reasoning: true`, `thinking.levels`, `compat.supportsReasoningEffort`를 내보내 GJC에서 강도를 선택할 수 있게 합니다. 네이티브 Codex 모델은 카탈로그에 단계가 없어도 표준 단계를 내보냅니다. 알려진 단계가 없는 모델은 이 필드들을 생략합니다. `none`은 강도를 보내지 않고 `ultra`는 전송 시 `max`로 바뀌므로 선택지에서 제외합니다. 모델 옵션을 갱신하려면 연동을 새로 고치세요. -관리형 OpenCode 연동은 `provider.opencodex`(opencode V1)와 `providers.opencodex`(opencode V2) 두 조각을 소유합니다. 모델별 추론 강도 변형은 V2 블록에만 있으므로 둘 다 기록하고 동기화합니다. 두 블록은 같은 프로바이더와 모델 ID를 가리키고 opencode V2는 이를 프로바이더 항목 하나로 병합합니다. Apply, Refresh, Disable, Restore는 두 조각 모두에 작용하며 다른 프로바이더, 에이전트, 단축키, MCP 항목은 유지됩니다. +관리형 OpenCode 연동은 `provider.opencodex`(opencode V1)와 `providers.opencodex`(opencode V2) 두 조각을 소유합니다. 두 블록 모두 선언된 모델별 추론 선택지와 기본값을 같은 내용으로 가집니다. 레거시 블록은 모델 options의 기본값과 변형 맵으로, V2 블록은 모델 settings와 네이티브 변형 배열로 표현하며 둘 다 기록하고 동기화합니다. 두 블록은 같은 프로바이더와 모델 ID를 가리키고 opencode V2는 이를 프로바이더 항목 하나로 병합합니다. Apply, Refresh, Disable, Restore는 두 조각 모두에 작용하며 다른 프로바이더, 에이전트, 단축키, MCP 항목은 유지됩니다. 이 모델 정의는 Kilo 내보내기와 `ocx opencode` 런처와 같은 유효 정규 모델 메타데이터에서 만들어집니다. 명시적 사용자 지정 오버라이드는 항상 우선하고 비워 둔 단계는 그대로 유지되며, 단계나 기본값을 지어내지 않습니다. 신뢰할 수 있는 컨텍스트 한도는 출력 한도와 함께 전달됩니다. 출력은 알려진 값(명시적 또는 카탈로그 메타데이터)이 우선하고, 알려지지 않았으면 스키마 필수 32000 폴백(컨텍스트로 상한)이 사용되며, 신뢰할 수 있는 컨텍스트가 없으면 limit 블록 전체가 생략됩니다. 기능, 강도 단계, 기본값, 선택적 입력 한도는 알려진 경우에만 기록됩니다. V2 블록의 네이티브 capabilities 객체에는 알려진 tools 값이 필요해서 도구 지원 여부를 알 수 없으면 일부만 쓰지 않고 객체 전체를 생략하며, 레거시 쪽 모달리티 선언은 그대로 남습니다. 단계를 지어내는 대신 제어만 기록합니다. V2 블록은 항상 명시적인 변형 배열을 가지며 선언이 없으면 비어 있는데, 생략하면 OpenCode가 low/medium/high를 합성하기 때문입니다. 고정된 추론은 알려졌지만 조절 가능한 단계가 없는 모델에는 선택 가능한 강도 대신 비활성 전용 억제가 기록됩니다. 레거시 OpenCode 블록과 Kilo 모두 클라이언트가 그냥 생성했을 각 단계 ID를 전부 비활성화합니다. 어느 쪽도 선택 가능한 강도를 추가하지 않습니다. 선택 가능한 변형(모델 자신의 단계가 `none`을 선언한 경우의 `none` 포함)은 알려진 경우에 기록되는 기본 추론 강도를 덮어쓸 수 있습니다. 클라이언트가 무엇을 선택하든 프록시의 업스트림 고정 정책은 계속 실제 요청에 적용됩니다. 관리형 DSH 지원의 최저 호환 버전은 **DSH 0.1.0-rc.6**입니다. opencodex는 `llm-pi-ai.providers.opencodex`만 소유합니다. Apply와 Refresh는 해당 조각을 교체하고, Disable은 그 조각만 제거하며, Restore는 기록된 스냅샷을 되돌립니다. DSH는 프로바이더 변경을 즉시 다시 읽습니다. 이 작업은 사용자의 기본 모델이나 네이티브 `deepseek-official` 프로바이더를 바꾸지 않습니다. 관리형 DSH 연동은 현재 루프백 전용이며 실제 자격 증명을 기록하지 않습니다. @@ -115,7 +115,7 @@ TOML 날짜와 시간도 관리형 재작성을 거부합니다. 병합 과정 **Kimi Code는 환경 변수 참조를 담을 수 없어** 설정에 키 대신 `opencodex-loopback` 자리표시자를 넣습니다. 어느 클라이언트 설정에도 실제 자격 증명을 기록하지 않습니다. -**`ocx opencode`에서는 런처의 프로바이더 블록이 우선합니다.** 런처는 `OPENCODE_CONFIG_CONTENT`를 통해 `provider.opencodex`와 `providers.opencodex`를 주입합니다. 디스크의 같은 항목보다 우선하지만 opencode 설정의 나머지는 평소처럼 적용됩니다. 여기의 스위치는 `opencode`를 직접 실행할 때 중요합니다. +**`ocx opencode`에서는 런처의 프로바이더 블록이 우선합니다.** 런처는 `OPENCODE_CONFIG_CONTENT`를 통해 `provider.opencodex`와 `providers.opencodex`를 주입합니다. 디스크의 같은 항목보다 우선하지만 opencode 설정의 나머지는 평소처럼 적용됩니다. 이 블록은 내보내기와 같은 유효 정규 모델 메타데이터로 구성되므로 런처와 관리형 연동이 같은 모델, 한도, 추론 선택지를 기술합니다. 여기의 스위치는 `opencode`를 직접 실행할 때 중요합니다. ## 터미널에서 사용하기 @@ -144,7 +144,7 @@ ocx integration client enable --client mcode ocx mcode ``` -연결 후 `ocx sync`와 `POST /api/sync`는 소유한 MCode, Pi, Aside, Raycast, omo 카탈로그를 현재 모델 선택, 컨텍스트 창, 추론 강도 단계로 갱신합니다. 프록시 시작 시 소유한 Raycast 카탈로그도 갱신합니다. 모델 표시 여부, 프로바이더 선택, 프리셋이 바뀌어도 연결된 Pi, Aside, Raycast, omo 카탈로그를 갱신합니다. 누락되거나 외부에서 수정되었거나 안전하지 않은 블록, 그리고 이전에 소유했지만 사용자가 직접 삭제한 블록은 그대로 둡니다. 활성화된 Aside 프로필은 일반적인 소유 블록만 갱신하는 규칙의 예외입니다. 계정 디렉터리가 있고 소유 블록이 생긴 적이 없다면 해당 슬롯이 비어 있을 때 동기화로 첫 블록을 만들 수 있습니다. 이전 Aside 연결이 있으면 이 동작이 기본적으로 등록된 모든 프로필에 적용됩니다. 동기화는 없는 계정 디렉터리를 만들거나 수동 블록을 교체하지 않습니다. 거부되거나 겹친 갱신은 클라이언트마다 따로 보고합니다. 갱신 파일을 읽으려면 새 Pi 세션을 시작하거나 Aside를 완전히 종료하고 다시 여세요. Aside 갱신에는 [호환되는 실행 중 프록시](#aside-프로필-제어)가 필요합니다. +연결 후 `ocx sync`와 `POST /api/sync`는 소유한 MCode, Pi, Aside, Raycast, omo, OpenCode, Kilo 카탈로그를 현재 모델 선택, 컨텍스트 창, 추론 강도 단계로 갱신합니다. 프록시 시작 시 소유한 Raycast 카탈로그도 갱신합니다. 모델 표시 여부, 프로바이더 선택, 프리셋이 바뀌어도 연결된 Pi, Aside, Raycast, omo, OpenCode, Kilo 카탈로그를 갱신합니다. 누락되거나 외부에서 수정되었거나 안전하지 않은 블록, 그리고 이전에 소유했지만 사용자가 직접 삭제한 블록은 그대로 둡니다. 활성화된 Aside 프로필은 일반적인 소유 블록만 갱신하는 규칙의 예외입니다. 계정 디렉터리가 있고 소유 블록이 생긴 적이 없다면 해당 슬롯이 비어 있을 때 동기화로 첫 블록을 만들 수 있습니다. 이전 Aside 연결이 있으면 이 동작이 기본적으로 등록된 모든 프로필에 적용됩니다. 동기화는 없는 계정 디렉터리를 만들거나 수동 블록을 교체하지 않습니다. 거부되거나 겹친 갱신은 클라이언트마다 따로 보고합니다. 갱신 파일을 읽으려면 새 Pi, OpenCode, Kilo 세션을 시작하거나 Aside를 완전히 종료하고 다시 여세요. Aside 갱신에는 [호환되는 실행 중 프록시](#aside-프로필-제어)가 필요합니다. Models에 **“Model selection saved”**와 클라이언트 갱신 경고가 함께 표시되면 선택 자체는 저장되었지만 클라이언트 파일 하나 이상을 갱신하지 못한 상태입니다. 경고는 해당 클라이언트와, 필요하면 Aside 프로필을 알려주고 거부 이유를 설명합니다. 새 세션을 시작하기 전에 **Integrations**에서 해당 클라이언트나 프로필을 확인하세요. 문제를 해결한 뒤 `ocx sync`를 다시 실행합니다. 겹친 작업은 먼저 끝나야 합니다. 경고에 백업 경로가 있거나 복구가 완료되지 않았다고 나오면 재시도 전 복구 상태를 확인하세요. 선택 저장 성공만으로 클라이언트 파일 복구까지 확인된 것은 아닙니다. @@ -222,9 +222,9 @@ Kilo CLI, VS Code, JetBrains는 전역 설정을 공유합니다. 이 연동은 Kilo는 이 전역 파일을 모두 병합합니다. 다른 후보 파일에도 `provider.opencodex`가 있으면 상태에 충돌 파일을 표시하고 적용과 교체를 거부합니다. 연동을 켜기 전에 해당 파일에서 `provider.opencodex`를 제거하세요. 이미 소유한 파일의 블록은 충돌 중에도 비활성화할 수 있습니다. 읽을 수 없거나 안전하지 않은 후보 파일도 쓰기를 막습니다. -관리하는 부분은 OpenCode V1 형식의 `provider.opencodex`(`npm`, `options`, `models`)뿐입니다. OpenCode V2의 `providers` 키는 내보내지 않습니다. `$schema`, `model`, `enabled_providers`, MCP 등의 키는 사용자가 관리합니다. 적용한 뒤 Kilo에서 `opencodex/`을 선택하세요. +관리하는 부분은 OpenCode V1 형식의 `provider.opencodex`(`npm`, `options`, `models`)뿐입니다. OpenCode V2의 `providers` 키는 내보내지 않습니다. 대신 모델별 추론 선택지는 프로바이더 블록 안의 변형 맵으로, OpenCode 내보내기와 같은 유효 정규 메타데이터에서 기록됩니다. `$schema`, `model`, `enabled_providers`, MCP 등의 키는 사용자가 관리합니다. 적용한 뒤 Kilo에서 `opencodex/`을 선택하세요. -루프백에서는 `options.apiKey`로 `{env:OPENCODEX_KILO_API_KEY}`를 사용합니다. 루프백이 아닌 바인드에서는 인증을 `options.headers["x-opencodex-api-key"]`로 옮기며 실제 키를 저장하지 않습니다. 적용 시 전역 파일 전체를 보기 좋은 JSON으로 다시 쓰므로 다른 키의 주석과 후행 쉼표는 보존되지 않습니다. Kilo는 자동 카탈로그 갱신 대상이 아닙니다. 라우팅 모델 선택을 바꾼 뒤에는 명시적으로 갱신하세요. +루프백에서는 `options.apiKey`로 `{env:OPENCODEX_KILO_API_KEY}`를 사용합니다. 루프백이 아닌 바인드에서는 인증을 `options.headers["x-opencodex-api-key"]`로 옮기며 실제 키를 저장하지 않습니다. 적용 시 전역 파일 전체를 보기 좋은 JSON으로 다시 쓰므로 다른 키의 주석과 후행 쉼표는 보존되지 않습니다. Kilo는 자동 카탈로그 갱신 대상입니다. `ocx sync`, `POST /api/sync`, 표시 여부·프로바이더·프리셋 변경은 다른 클라이언트와 같은 소유 블록만 갱신하는 안전한 규칙으로 소유한 Kilo 블록을 갱신하며, 새 Kilo 세션이 갱신된 파일을 읽습니다. ```bash ocx integration client enable --client kilo @@ -258,4 +258,28 @@ GitHub Copilot 데스크톱 앱에서 opencodex를 OpenAI 호환 모델 프로 ## Factory Droid +**연동 → Factory Droid** (`/#integrations/droid`)에서 연결된 모델별 추론 기본값을 +설정할 수 있습니다. 모델이 지원하는 effort를 선택하고 변경 내용을 검토한 뒤 +확인하세요. **기본값 없음**은 해당 모델의 편집 중인 값을 지웁니다. 실제로 적용하려면 +**변경 사항 저장 / 검토**에서 확인해야 합니다. 지원 effort +목록이 없는 모델에는 사용 가능한 기본값이 없다고 표시됩니다. + +이 값은 Droid 요청에 effort가 없을 때만 적용됩니다. 요청이 effort를 명시하면 +그 값을 사용하며, 기존 OpenCodex pin과 cap 정책은 계속 적용됩니다. OpenCodex가 +기본값을 적용하더라도 Droid에는 **Dynamic**으로 표시될 수 있습니다. Droid 기본값 +헤더가 없는 요청에는 영향을 주지 않습니다. 이 헤더는 요청의 선호 값이며, +클라이언트 신원을 증명하지 않습니다. +콤보와 라우팅 정책에서는 각 실제 대상이 자체 effort 목록으로 이 선호 값을 +확인하므로, 첫 대상이 지원하지 않아도 지원하는 다음 대상으로 전달됩니다. + +저장된 기본값을 라우팅된 모델이 더 이상 지원하지 않으면 요청에는 적용하지 +않습니다. 현재 모델의 effort 목록에서 빠진 값은 패널의 기본값에서도 제외되며, +새로고침할 때 관리 행에서 제거됩니다. 편집 없이 변경 내용을 검토하면 여전히 +지원되는 기본값은 유지됩니다. + +새로고침은 정확히 같은 `provider/model` 선택자가 계속 연결되고 저장된 effort를 지원하는 동안 기본값을 +유지합니다. 프로바이더, 모델, 콤보 별칭의 이름을 바꾸면 관리 행이 교체되고 +기본값이 지워지므로 새 행에서 다시 선택하세요. 연동을 해제하면 관리되는 모델 +행과 기본값이 함께 제거되며, 되돌리기는 저장된 행과 기본값을 함께 복원합니다. + Factory Droid는 `~/.factory/settings.json`(Windows에서는 `%USERPROFILE%\.factory\settings.json`)을 사용합니다. `ocx integration client enable --client droid`로 명시적으로 활성화한 다음 `/model`에서 사용자 지정 모델을 선택하세요. 관리되는 항목에는 키가 없으며 루프백에서만 동작합니다. 비활성화하면 관리되는 항목이 제거되고, Undo는 저장된 원본 바이트를 복원합니다. 기존 `config.json`에 OpenCodex 항목이 있거나 `settings.local.json`이 `customModels`를 덮어쓰면 활성화 전에 충돌을 해결하세요. [Factory BYOK 문서](https://docs.factory.ai/model-independence/byok)를 참고하세요. diff --git a/docs-site/src/content/docs/ko/guides/remote-link.md b/docs-site/src/content/docs/ko/guides/remote-link.md index 6ac312feeb9..0198c2ef3ae 100644 --- a/docs-site/src/content/docs/ko/guides/remote-link.md +++ b/docs-site/src/content/docs/ko/guides/remote-link.md @@ -11,7 +11,7 @@ description: SSH로 OpenCodex Home 컴퓨터와 Child 컴퓨터를 연결합니 - 자식이 시작하는 링크에서는 자식에서 OpenSSH 키 로그인으로 홈에 접속할 수 있어야 합니다(비밀번호 로그인은 지원하지 않음). - Child 컴퓨터에 OpenCodex 2.66.0 이상이 설치되어 있어야 합니다(자식이 시작하는 링크에서는 Home에도). - 두 컴퓨터 모두 macOS 또는 Linux여야 합니다. -- 링크를 시작하는 대시보드는 그 컴퓨터에서 직접 열거나(독립형 설치의 브라우저 또는 데스크톱 앱) 페어링된 Hub 세션으로 열어야 합니다. +- Home에서 Child를 추가하는 대시보드는 Home에서 직접 열거나 페어링된 Hub 세션을 사용해야 합니다. 현재 컴퓨터를 Child로 전환하려면 운영자가 페어링한 대시보드 세션이 필요하며, 자격 증명 없이 발급된 로컬 세션은 이 라우팅 변경을 확정할 수 없습니다. 비밀번호 SSH와 Windows는 현재 흐름에서 지원하지 않습니다. 링크는 어느 쪽에서든 시작할 수 있습니다. 아래 순서대로 Home에서 시작하거나, 이어지는 "이 컴퓨터를 Child로 연결하기" 절처럼 Child에서 시작합니다. @@ -25,6 +25,17 @@ description: SSH로 OpenCodex Home 컴퓨터와 Child 컴퓨터를 연결합니 대시보드는 토큰 입력을 요구하지 않습니다. 먼저 호스트를 검사하며, 지문을 명시적으로 확인하기 전에는 링크를 적용하지 않습니다. +Child 추가 창은 Child가 SSH를 통해 이 Home의 공급자를 사용한다는 점과 위의 사전 요구 사항을 안내합니다. 별칭은 브라우저를 표시하는 컴퓨터가 아니라 OpenCodex를 실행하는 Home의 `~/.ssh/config`에서 가져옵니다. 검색은 성공했지만 호스트가 없다면 아래와 같은 `Host` 항목을 추가하고 다시 검색하세요. 기존 별칭을 직접 입력할 수도 있으며, 별칭을 선택하거나 입력하면 연결 테스트가 활성화됩니다. 창에서 이 가이드도 열 수 있습니다. + +```sshconfig +Host devbox + HostName devbox.example.com + User you + IdentityFile ~/.ssh/id_ed25519 +``` + +검색에 실패하면 빈 목록이라고 표시하지 않고 불러오기 실패, 확인 가능한 원인과 재시도 버튼을 표시합니다. 연결 테스트의 구체적인 실패 원인과 정제된 SSH 힌트는 활성 창에만 표시하며 뒤쪽 페이지에 중복 표시하지 않습니다. 원인과 아래 SSH 진단 방법을 확인하고 다시 시도하세요. 다시 검색해도 입력한 별칭은 유지되지만 연결 전에 지문을 새로 확인해야 합니다. + ## 이 컴퓨터를 Child로 연결하기 Home의 프로바이더를 사용할 컴퓨터에서 다음을 진행합니다. @@ -36,7 +47,9 @@ Home의 프로바이더를 사용할 컴퓨터에서 다음을 진행합니다. 연결하면 이 컴퓨터의 OpenCodex가 다시 시작됩니다. 이미 실행 중인 Codex 작업은 먼저 끝나고, 다시 시작되는 동안 최대 1분 정도 새 요청이 실패할 수 있습니다. 그 뒤 대시보드가 스스로 새로 고쳐지고 Child 링크를 보여 줍니다. Codex는 이 컴퓨터의 `http://127.0.0.1:/v1`을 그대로 사용하며 토큰이나 환경 변수를 설정할 필요가 없습니다. 로컬 OpenCodex가 각 요청을 Home으로 전달하고, Home은 자신의 프로바이더와 계정으로 응답합니다. -**Child** 역할은 OpenCodex가 설정된 포트에서 실행 중일 때만 선택할 수 있습니다. Child는 정확히 그 포트에서 다시 시작하기 때문입니다. 대시보드가 설정된 포트에서 실행되고 있지 않다고 알리면, 먼저 그 포트에서 OpenCodex를 다시 시작하세요. +**Child**에 이 컴퓨터를 먼저 페어링하라는 안내가 나오면, 이 컴퓨터에 설정된 IP 주소 형식의 HTTP 루프백 대시보드(예: `http://127.0.0.1:`)를 여세요. 로컬 페어링 양식은 페어링이 필요하고 대시보드와 API가 같은 루프백 출처를 사용할 때만 표시됩니다. 양식의 `ocx gui pair --origin "http://127.0.0.1:"` 명령을 복사해 이 컴퓨터의 터미널에서 실행한 뒤, 일회용 코드를 양식에 붙여 넣으세요. 양식에 표시된 정확한 출처를 사용해야 하며, 프로바이더 API 키나 관리자 토큰은 페어링 코드가 아닙니다. 페어링 누락과 설정된 포트의 불일치는 서로 다른 사유입니다. + +**Child** 역할을 선택하려면 OpenCodex가 독립형 런타임으로 설정된 포트에서 실행 중이어야 합니다. Child는 정확히 그 포트에서 다시 시작하기 때문입니다. 대시보드가 설정된 포트에서 실행되고 있지 않다고 알리면, 먼저 그 포트에서 OpenCodex를 다시 시작하세요. ## 링크 상태 diff --git a/docs-site/src/content/docs/ko/reference/cli/agents.md b/docs-site/src/content/docs/ko/reference/cli/agents.md index 38ea7dc4cd8..4a1f4c79839 100644 --- a/docs-site/src/content/docs/ko/reference/cli/agents.md +++ b/docs-site/src/content/docs/ko/reference/cli/agents.md @@ -275,3 +275,15 @@ ocx system codex-cli-update attest --candidate --npm-prefix [--json]` +Anthropic의 자동 대체 계정 선택은 기존 계정 순서를 유지하며, 일시 정지되었거나 재인증이 필요한 계정과 60초 이내에 만료되는 Claude Code 가져오기를 건너뜁니다. 출처가 기록되지 않은 레거시 계정도 선택할 수 있지만, 자체 저장 자격 증명과 일반적인 저장 토큰 갱신만 사용하며 CLI 디스크 자격 증명을 가져오지 않습니다. 60초 넘게 남은 유효한 Claude Code 계정은 선택할 수 있습니다. 이후 자동 가져오기는 비어 있지 않은 access 또는 refresh 토큰이 일치하면 허용됩니다. 두 토큰이 모두 바뀌면 저장된 bearer와 새 bearer에서 인증된 계정 UUID가 같아야 합니다. 로그인·갱신·프로필 조회에서 얻은 증명은 비공개로 저장하며, 계정 라벨·이메일·조직·파일 경로로 대체하지 않습니다. + +기존 bearer가 만료되었고 해당 토큰에 묶인 계정 증명이 없으면 자동 복구가 불가능할 수 있습니다. 프로필을 조회할 수 없거나 교체된 토큰의 계정을 확인하지 못하면 저장된 계정을 유지하고, 이미 소비되었을 수 있는 refresh 토큰을 다시 사용하지 않습니다. 현재 Claude Code 자격 증명을 가져오려면 명시적으로 로그인하세요. 가져오기는 관련 없는 신원 미확인 슬롯을 보존하므로 별도 계정이 생길 수 있습니다. 사용할 계정을 선택하고 확인한 뒤 오래된 슬롯을 삭제하세요. 프로필 조회에 실패한 새 가져오기도 신원 미확인 상태로 남으며 같은 자동 복구 제한이 적용됩니다. + +허용된 대체 계정이 없으면 사용 가능한 활성 계정이 선택될 때까지 할당량 조회와 실시간 모델 검색이 대기합니다. +`ocx account use anthropic `로 일시 정지되지 않은 기존 레거시 계정을 명시적으로 선택할 수 있습니다. +원래 자격 증명의 출처가 기록되지 않았더라도 해당 계정의 유효한 자격 증명과 일반적인 저장 토큰 갱신을 계속 사용할 수 있습니다. + CLI 명령은 Anthropic OAuth 계정을 id 또는 유일한 alias로 일시 정지하거나 재개합니다. alias는 정확히 일치하는 값을 먼저 찾고, 없으면 대소문자를 구분하지 않고 찾습니다. 대시보드와 같은 `PUT /api/oauth/accounts/pause`에 `{ provider: "anthropic", accountId, paused }`를 보냅니다. 계정에 저장되는 `paused` 상태는 `GET /api/oauth/accounts`에도 표시됩니다. 사전 계정 전환 풀이 꺼져 있어도 정지된 계정은 선택, 세션 바인딩, 429 대체 후보에서 제외됩니다. 모든 계정이 정지되면 하나를 재개할 때까지 요청은 403을 반환합니다. 이미 전송한 요청은 유지하며 자격 증명과 건강 상태를 지우지 않습니다. 재시작·재로그인 후에도 정지는 유지되고, 계정을 삭제하면 함께 제거됩니다. 계정별 전환 임계값은 이 기능에 포함되지 않습니다. ### `ocx account clear [--json]` diff --git a/docs-site/src/content/docs/ko/reference/configuration/server.md b/docs-site/src/content/docs/ko/reference/configuration/server.md index ee24fd4f16e..14c89eaa329 100644 --- a/docs-site/src/content/docs/ko/reference/configuration/server.md +++ b/docs-site/src/content/docs/ko/reference/configuration/server.md @@ -244,3 +244,19 @@ Anthropic OAuth 사이드카는 opencodex의 기존 Claude Code OAuth fingerprin ## Codex 할당량 네트워크 진단 메인 Codex 계정 행의 `quotaRefresh`는 할당량 조회 결과를 분류하는 진단값입니다. 남은 할당량이나 모델 접근 권한을 뜻하지 않으며, 캐시를 쓰거나 조회하지 않았다면 생략될 수 있습니다. 요청은 명령을 입력한 터미널이 아니라 실행 중인 프록시 서비스의 환경을 따릅니다. `proxy`를 지정하지 않으면 기존 환경을 유지하고, `"auto"`는 시작 시 Windows 또는 macOS의 정적 HTTP/HTTPS 설정을 읽습니다. macOS에서는 상속된 프록시가 있으면 읽지 않습니다. macOS에서는 유효한 `*.`을 `.`으로 바꿉니다. `*.local`은 `foo.local`과 최상위 이름 `local`을 직접 연결하지만 `xlocal`은 제외합니다. `169.254/16`, `169.254.0.0/16`, `fe80::/10`은 진단 메시지와 함께 생략하므로 링크 로컬 IP 주소는 프록시를 사용합니다. IP 주소와 `*`는 허용하지만 다른 CIDR, glob, 단순 호스트명 예외는 환경 변경 전에 탐색을 거부합니다. PAC/WPAD, SOCKS 전용 설정과 실행 중 변경은 자동으로 반영하지 않습니다. TUN에서 성공했다고 HTTP 프록시 경로도 정상이라는 뜻은 아닙니다. 명령과 상태값은 [네트워크 진단(영문)](/reference/configuration/server/#codex-quota-network-diagnostics)에서 확인하세요. + +### Forced Claude Code subagent model + +The Subagents page offers **Force all subagents onto one model**, off by default. Select an exposed roster-style id, such as `combo/tev-auto`, then enable the switch. The roster is offered first; unavailable saved roster entries cannot be force targets. + +`ocx agent subagents force combo/tev-auto` sets `claudeCode.subagentModelForce`; `ocx agent subagents force -` clears it. `ocx agent status` reports the setting. `GET /api/subagent-models` returns `force`, `forceAvailable`, and `forceStatus`; `PUT` accepts `{ "force": "combo/tev-auto" }` or `{ "force": null }` without changing the roster. Omitting `force` leaves it unchanged. Invalid or unexposed targets are rejected on write; stale targets are reported and skipped at launch. + +This takes effect on the **next routed `ocx claude` launch**, injecting `CLAUDE_CODE_SUBAGENT_MODEL` as an explicit proxy alias (with `[1m]` only for an authoritative million-token window; native Claude targets use a reversible native alias) and `CLAUDE_CODE_SUBAGENT_MODEL_FORCE=1`. Each nonempty shell-exported variable independently wins. Native launches inject neither variable; plain `claude` is not affected. No plugin files or `settings.json` are modified by this setting. + +Claude Code **2.1.257 or newer** is required for FORCE. Plugin and built-in agents (including Explore/Plan) and per-call model arguments are overridden. Forks and subagent skills with `model: inherit` keep the main conversation model. The main loop and Haiku/small-fast sidecars are unaffected. Existing roster files remain available. + +The dashboard warns about old or unknown CLI versions, unavailable targets, and either variable already present in `settings.json` → `env` (which overrides launch env). Detection is read-only and server-local: it cannot inspect another launch shell, another machine, or project-local settings. An unknown result is not proof of force support. + +## 토큰 예약과 한도 + +요청에 `spend.root.maxTokens`, `spend.identity.maxTokens`, `spend.pool.maxTokens` 중 하나가 적용되면 추적 용량 부족 등으로 토큰 예약을 기록할 수 없을 때 전송을 거부합니다. 적용되는 한도가 없는 요청은 계속 관측만 합니다. 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 a38309cb2cc..bdabc85256a 100644 --- a/docs-site/src/content/docs/ko/reference/management-api.md +++ b/docs-site/src/content/docs/ko/reference/management-api.md @@ -390,3 +390,15 @@ Anthropic OAuth 전용. `{ provider: "anthropic", accountId, threshold }`: 정 계정 DTO는 `autoSwitchThresholdOverride`(정수/null), `autoSwitchThreshold`(풀 기본값), `effectiveAutoSwitchThreshold`를 포함합니다. 0은 사용량 전환만 끄며 pause·429 복구는 유지합니다. HTTP: 400 invalid/unsupported; 404 missing account; `oauth_mutation_busy` on lock contention. + +### Forced Claude Code subagent model + +The Subagents page offers **Force all subagents onto one model**, off by default. Select an exposed roster-style id, such as `combo/tev-auto`, then enable the switch. The roster is offered first; unavailable saved roster entries cannot be force targets. + +`ocx agent subagents force combo/tev-auto` sets `claudeCode.subagentModelForce`; `ocx agent subagents force -` clears it. `ocx agent status` reports the setting. `GET /api/subagent-models` returns `force`, `forceAvailable`, and `forceStatus`; `PUT` accepts `{ "force": "combo/tev-auto" }` or `{ "force": null }` without changing the roster. Omitting `force` leaves it unchanged. Invalid or unexposed targets are rejected on write; stale targets are reported and skipped at launch. + +This takes effect on the **next routed `ocx claude` launch**, injecting `CLAUDE_CODE_SUBAGENT_MODEL` as an explicit proxy alias (with `[1m]` only for an authoritative million-token window; native Claude targets use a reversible native alias) and `CLAUDE_CODE_SUBAGENT_MODEL_FORCE=1`. Each nonempty shell-exported variable independently wins. Native launches inject neither variable; plain `claude` is not affected. No plugin files or `settings.json` are modified by this setting. + +Claude Code **2.1.257 or newer** is required for FORCE. Plugin and built-in agents (including Explore/Plan) and per-call model arguments are overridden. Forks and subagent skills with `model: inherit` keep the main conversation model. The main loop and Haiku/small-fast sidecars are unaffected. Existing roster files remain available. + +The dashboard warns about old or unknown CLI versions, unavailable targets, and either variable already present in `settings.json` → `env` (which overrides launch env). Detection is read-only and server-local: it cannot inspect another launch shell, another machine, or project-local settings. An unknown result is not proof of force support. diff --git a/docs-site/src/content/docs/reference/adapters.md b/docs-site/src/content/docs/reference/adapters.md index 130e9e91e27..c616f58a0ed 100644 --- a/docs-site/src/content/docs/reference/adapters.md +++ b/docs-site/src/content/docs/reference/adapters.md @@ -118,6 +118,10 @@ be configured on a separately named custom or self-hosted Ollama provider with is refused rather than mis-sent, and remote image URLs are not fetched. - **Tools:** declared in Ollama's native shape, streamed tool calls are whole-call records with object-valued `arguments`, and tool-result replay is paired strictly by call id and tool name. + Codex may record assistant commentary before a pending call's results. Text/thinking with no + new tool calls is deferred until the batch is settled, so genuine results remain beside their + originating calls. A new tool-call batch still settles the preceding one; missing results retain + an explicit unknown-status marker, and orphan or duplicate results remain invalid. `tool_choice: "none"` and `auto` behave normally; **`required` or an exact named choice fails closed**, because Ollama's `/api/chat` has no `tool_choice` field to enforce it with. - **Structured output is refused on canonical Ollama Cloud.** Ollama currently documents structured @@ -137,8 +141,10 @@ configured provider key. The adapter preserves the incoming client's `User-Agent` as a fallback in both auth modes because some Responses-compatible providers use the Codex client fingerprint for compatibility behavior. An explicitly configured provider `User-Agent` remains authoritative regardless of header casing; -if the caller sends none, OpenCodex does not invent one. No other caller header is widened by this -exception. +if the caller sends none, OpenCodex does not invent one. Additional caller metadata can be selected +with `forwardClientHeaders`; provider `headers` win for this option, and credential or transport-owned +names are refused. Canonical ChatGPT forward auth retains its separate fixed header allowlist. +Only `originator`, `x-client-request-id`, `x-codex-app-version`, and `user-agent` are supported by `forwardClientHeaders`; arbitrary names are rejected on load/write and ignored at runtime. Adapter selection does not select the upstream transport. Eligible requests can use the [upstream WebSocket proxy route](/reference/proxy-formats/#json-and-sse-output); invalid or unsupported diff --git a/docs-site/src/content/docs/reference/cli.md b/docs-site/src/content/docs/reference/cli.md index 2cf78314711..7bb9e2f1dc6 100644 --- a/docs-site/src/content/docs/reference/cli.md +++ b/docs-site/src/content/docs/reference/cli.md @@ -7,13 +7,89 @@ The opencodex CLI is `ocx`. It dispatches on the first command name, with docume as `setup`/`init`, `restore`/`eject`, and `models`/`model` reaching the same operation. Unknown commands and invalid command shapes are errors. -Run `ocx help` (or `ocx --help` / `ocx -h`) for top-level usage. Run `ocx help `, -`ocx --help`, or `ocx -h` for a command registered in the help table. Help and -version commands are read-only: they do not start, stop, install, uninstall, or rewrite Codex or +Run `ocx`, `ocx help`, `ocx --help`, or `ocx -h` for the same compact command index, +grouped into Start here, Common tasks, Explore, and More help. +Use `ocx help --all` or `ocx --help --all` for the full top-level reference, including +the detailed command variants omitted from the compact index. +Run `ocx help `, `ocx --help`, or `ocx -h` for a command +registered in the help table; `ocx help` also remains supported. Help and version +commands are read-only: they do not start, stop, install, uninstall, or rewrite Codex or opencodex state. +## Nested help + +Family help such as `ocx help models` preserves the family's usage and details, then +lists known declared child paths with summaries. These lists are marked as partial; +they do not enumerate every runtime operation. Alias help retains the alias's own usage +and details and points to its canonical family: `ocx help model` leads to `ocx help models`. +Models help also links the curated `models context` topic separately from declared capabilities. + +Help accepts a path with more than one command word: + +```bash +ocx help models context +ocx models context --help +ocx model context --help +ocx help account list +ocx help account main +``` + +The first three forms show the same context-cap topic, including the `model` alias: + +```text +ocx models context [--set-all]|provider on [--value ]|provider off|all > [--json] +``` + +`ocx models context status --json` reads the current settings. `value ` sets the +default for future toggles; adding `--set-all` applies it to all routed providers. +`provider on` enables a provider cap, optionally with `--value `; `off` +disables it. `all on|off` changes all routed providers together. + +Declared capability topics such as `account list` show their summary, known flags and +details, with a pointer to parent help. This metadata does not cover every operand or runtime +subcommand, so a topic can show `Command: ocx ...` without claiming a complete `Usage:` grammar. +A prefix such as `account main` lists its declared children and marks that coverage as incomplete. +An undeclared explicit topic (`ocx help `) exits 1 with a concise detailed-help-unavailable +message and a known-parent or full-reference pointer on standard error. Standard output stays +empty, and no full help banner is printed. Missing detail does not establish whether the runtime command is valid. +Appended `--help`/`-h` preserves existing command-help behavior: when detailed metadata is unavailable, +it displays known parent help successfully, without executing the command. + +In the Bun CLI head, bare `help` is recognized only at the root or immediately after the root +command. Use `--help` or `-h` for nested paths. Later values such as the `help` in +`ocx alias set demo help` remain command arguments. An exact `--` ends head help scanning, +so `ocx claude -- --help` preserves the arguments for command dispatch. + +## Recovering from command typos + +Unknown root commands and unresolved explicit help paths exit 1 with a short diagnostic and +navigation guidance on standard error, leaving standard output empty. Close typos can receive +conservative suggestions drawn from visible command names and the current help family's documented +children. Suggestions are guidance only: the CLI never executes them or retries the command. + +| Input | Guidance | +| --- | --- | +| `ocx modles` or `ocx help modles` | Suggests `ocx help models`. | +| `ocx help account lisst` | Suggests `ocx help account list` within the account family. | +| `ocx help qzxv` | Offers `ocx help --all` without guessing a command. | +| `ocx help service install` | Reports unavailable detailed help and points to `ocx help service`; the runtime install operation remains valid. | + +The Bun CLI rejects an unknown root before shim auto-restore or other command preflight. +Recognized commands retain their existing preflight; hidden commands and the internal runner +remain valid dispatch targets but are excluded from discovery and suggestions. Appended flag-help +fallback, capability JSON, and provider-specific error handling retain their existing behavior. + ## Command families +### `ocx provider` + +`ocx provider`, `ocx help provider`, `ocx provider help`, `ocx provider --help`, and +`ocx provider -h` show the same provider help, with command syntax, examples, preset/custom +guidance, and declared topic pointers. Successful help prints to standard output and exits 0. +Bare `ocx provider` still follows ordinary command preflight; explicit head-help forms exit +before that preflight. An unknown provider action prints its diagnostic and provider help +to standard error, leaves standard output empty, and exits 1. + ### `ocx alias` `ocx alias list [--json]` shows effective user and built-in aliases. Use `ocx alias set [/] ` and `ocx alias rm [/]` to edit them. Native model ids may contain additional slashes because the selector splits only at the first slash. Enable shipped defaults with `ocx alias defaults on|off [--provider ]`. @@ -119,8 +195,9 @@ a prompt an automated caller can answer is not a safety boundary, so the flag is ## Driving the CLI from an agent -`ocx capabilities --json` is the machine-readable index of every command, the management routes it -drives, its flags, and whether it mutates state. Start there rather than parsing help text: +`ocx capabilities --json` is the machine-readable index of declared capabilities, their management +routes, known flags, and mutation metadata. It is not an exhaustive command grammar or a list of +every runtime subcommand. Start there rather than parsing help text: ```bash ocx capabilities --json diff --git a/docs-site/src/content/docs/reference/cli/agents.md b/docs-site/src/content/docs/reference/cli/agents.md index 727a5699fc8..8a4cca70562 100644 --- a/docs-site/src/content/docs/reference/cli/agents.md +++ b/docs-site/src/content/docs/reference/cli/agents.md @@ -34,6 +34,9 @@ prints a proposed model and effort per role without writing anything. `--apply` through the same write as `set`, skipping and naming the roles whose model and effort already match. See [Auto-assign](/guides/integrations/#auto-assign). +Human-readable suggestion output displays terminal control characters as visible escapes. +Use `--json` when you need the original suggestion values without presentation escaping. + `ocx agent injection suggest ` does the same for the delegation model: it sizes the described work, proposes the cheapest sufficient model and an effort from the delegation picker's list, and writes nothing unless `--apply` is given, which saves through the same write as `injection set`. See @@ -305,8 +308,8 @@ remain supported. Use `ocx claude config ...` for Claude Code setti Ensure the proxy is running, then launch opencode with the generated `provider.opencodex` and `providers.opencodex` blocks in OpenCode's inline runtime layer (`OPENCODE_CONFIG_CONTENT`). The -legacy block keeps V1 clients working; the V2 block is the one carrying the selectable -reasoning-effort variants. Existing inline config is preserved and only those two keys are replaced +legacy block keeps V1 clients working with variant maps; the V2 block carries native arrays +for the same reasoning-effort choices and defaults. Existing inline config is preserved and only those two keys are replaced for this launch. Global or project `opencode.json` files may be read to warn about an existing override, but on-disk files are never modified. Routed models appear as `opencodex//`. Launching plain `opencode` later behaves exactly as before. @@ -326,6 +329,11 @@ reference or loopback placeholder — in the selected client's native format. The proxy must be running; the command resolves its live port, reads `/api/models`, and emits only models Codex can currently see. +OpenCode and Kilo exports preserve effective model limits, known capabilities, declared reasoning +choices and defaults, including metadata inherited by custom rows. Explicit overrides still win; +unknown capabilities and defaults are not invented. The OpenCode launcher uses the same metadata. +See [client integrations](/guides/integrations/) for managed refresh and upgrade behavior. + | Flag | Action | | --- | --- | | `--client ` | Required. Selects the client config dialect. | @@ -497,3 +505,15 @@ Human `ocx logs` output includes `id=`. Pass that value to `ocx logs ## Upstream error details When an upstream error envelope contains several message fields, OpenCodex uses the first nonblank string in its established priority order. Empty or malformed fields no longer hide a valid fallback diagnostic. + +### Forced Claude Code subagent model + +The Subagents page offers **Force all subagents onto one model**, off by default. Select an exposed roster-style id, such as `combo/tev-auto`, then enable the switch. The roster is offered first; unavailable saved roster entries cannot be force targets. + +`ocx agent subagents force combo/tev-auto` sets `claudeCode.subagentModelForce`; `ocx agent subagents force -` clears it. `ocx agent status` reports the setting. `GET /api/subagent-models` returns `force`, `forceAvailable`, and `forceStatus`; `PUT` accepts `{ "force": "combo/tev-auto" }` or `{ "force": null }` without changing the roster. Omitting `force` leaves it unchanged. Invalid or unexposed targets are rejected on write; stale targets are reported and skipped at launch. + +This takes effect on the **next routed `ocx claude` launch**, injecting `CLAUDE_CODE_SUBAGENT_MODEL` as an explicit proxy alias (with `[1m]` only for an authoritative million-token window; native Claude targets use a reversible native alias) and `CLAUDE_CODE_SUBAGENT_MODEL_FORCE=1`. Each nonempty shell-exported variable independently wins. Native launches inject neither variable; plain `claude` is not affected. No plugin files or `settings.json` are modified by this setting. + +Claude Code **2.1.257 or newer** is required for FORCE. Plugin and built-in agents (including Explore/Plan) and per-call model arguments are overridden. Forks and subagent skills with `model: inherit` keep the main conversation model. The main loop and Haiku/small-fast sidecars are unaffected. Existing roster files remain available. + +The dashboard warns about old or unknown CLI versions, unavailable targets, and either variable already present in `settings.json` → `env` (which overrides launch env). Detection is read-only and server-local: it cannot inspect another launch shell, another machine, or project-local settings. An unknown result is not proof of force support. diff --git a/docs-site/src/content/docs/reference/cli/lifecycle.md b/docs-site/src/content/docs/reference/cli/lifecycle.md index 64d990df618..e6b11cd11b5 100644 --- a/docs-site/src/content/docs/reference/cli/lifecycle.md +++ b/docs-site/src/content/docs/reference/cli/lifecycle.md @@ -677,7 +677,9 @@ clears the stale job. On Windows, `ocx service status` reports Task Scheduler registration separately from identity-verified OpenCodex proxy reachability. It does not print the localized `schtasks` table, -so the summary remains readable across Windows code pages. +so the summary remains readable across Windows code pages. Transactional-backup recovery +logs a fixed success message rather than the backup directory name; backup names containing +shell metacharacters do not become commands in that log message. On Windows, creating the Task Scheduler entry requires elevation. Recognized localized access-denied text keeps the existing guidance path. If that text is unreadable, the fallback @@ -706,6 +708,16 @@ If installation is refused or the resulting shim is unhealthy, the command exits the dashboard reports the failure reason. A healthy existing shim still counts as success. For Windows installations that expose only `codex.exe`, use `ocx service install` for autostart. +For fnm-managed Codex, installation resolves the temporary multishell directory to the durable +Node installation while preserving the launcher filename. If that directory cannot be resolved, +installation refuses instead of wrapping a temporary path. When Codex is selected, `ocx connect` +also reports shim readiness. This readiness check skips special-file PATH entries while preserving +the order of regular launchers, including npm and fnm symlinks. If a different PATH wrapper hides a healthy shim, fix PATH order; +reinstalling the same shim does not change which command your shell finds first. If the tracked shim +is healthy but no `codex` command is found, connect reports it as inactive and asks you to add its +directory to PATH. A failed PATH inspection reports activation as unverified instead of claiming +that no command exists. These warnings do not change the connect command's exit status. + Before an install or repair is committed, OpenCodex runs the saved launcher with `--version` while service startup is bypassed. It refuses the change and rolls back when the launcher resolves `codex` back to the shim, exits nonzero, exceeds five seconds, leaves descendants running, or @@ -839,6 +851,18 @@ Unix-only check. A failure aborts while the tray and proxy are still running. A then stopped before files are replaced; an installed service is rebuilt and started automatically, while a foreground installation prints `ocx start` as the next step. Dashboard update records redact profile/cache paths and UID/GID values before they are persisted. +When a stopped listener's literal IP address drops the liveness dial instead of refusing it +(for example on a tailnet), the updater briefly tries binding that same address and port. +A successful bind confirms absence at that instant; a failed bind or an inconclusive hostname +probe still prevents the update. + +On Windows, Scoop's default `nodejs` and `nodejs-lts` `npm` installations can be used +from the user home directory when their `current` junction stays inside the Node app +directory; the default persistent `bin` directory is also supported. Running inside +that installation (including its resolved version directory or persistent `bin`), +project-local `npm`, `NO_JUNCTION` version-directory entries, and custom Scoop roots +under the home remain excluded. + If the install step fails, the previous version stays installed and its service is restarted; the terminal output names the next step, and [Update Failed on Windows](/troubleshooting/update-failed/) covers finishing the update and the folders a failed attempt can leave behind. 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 839bc7161b4..a5453b3f12c 100644 --- a/docs-site/src/content/docs/reference/cli/providers-accounts.md +++ b/docs-site/src/content/docs/reference/cli/providers-accounts.md @@ -118,6 +118,10 @@ For Antigravity, an upstream `401` can refresh the rejected account’s OAuth cr retry the request once. The retry uses that credential’s Cloud Code Assist project. If refresh fails or no usable project is available, the request returns an authentication error; use the reauthentication flow above. A second `401` does not start another refresh/retry cycle. +A `403` asking to verify the account quarantines that credential as `needs-reauth(verify)`; +when account failover is enabled, the request can retry on another eligible account. Complete +Google's account verification, then run `ocx login google-antigravity`. Silent token refresh +does not clear this verification requirement. If OpenCodex cannot save the quarantine, this adapter exchange preserves the original `403` without another recovery send; the account has not been durably quarantined. If a sibling request cannot be built or admitted for sending, the exchange delivers the original `403` through normal error formatting. An enclosing combo or policy route can still apply its existing fallback rules. A proxy that is already running picks up the new credential without a restart: the CLI asks it to reload that one provider from disk, and the request carries no credential of its own. If the @@ -499,6 +503,27 @@ Anthropic pause applies even when proactive pooling is disabled, including sessi and does not interrupt a turn already sent. Removing the account removes its pause state. Per-account Anthropic auto-switch thresholds are not part of this control. +Anthropic's automatic pause fallback keeps account order, skips paused accounts and accounts +requiring reauthentication, and excludes Claude Code imports expiring within 60 seconds. +Legacy accounts without a recorded source remain eligible using only their own stored credentials +and normal stored-token refresh; they never adopt CLI-disk credentials. A still-valid Claude Code import with more time remaining +can be selected. Later automatic re-adoption accepts a shared, nonempty access or refresh token. +If both tokens rotate, OpenCodex requires authenticated account UUID proof for both the stored +and imported bearer. It saves that proof privately when available from login, refresh, or profile +lookup; account labels, email, organization and credential-file location cannot substitute for it. + +When the old bearer has expired and no bound account proof was saved, automatic recovery may be +impossible. An unavailable profile or unverified rotated pair leaves the stored account unchanged +and does not replay a potentially consumed refresh token. Use explicit login to import the current +Claude Code credential. Import preserves unrelated identityless slots and may create a separate +account; select the intended account and remove obsolete slots only after checking them. A profile +lookup failure can leave a new import identityless, with the same automatic-recovery limitation. + +If no permitted fallback remains, quota and live model discovery wait for a usable active account. +You can explicitly select an existing unpaused legacy account with +`ocx account use anthropic `; its own valid credential and normal stored-token +refresh remain available even when its original credential source was not recorded. + ```bash ocx account pause google-antigravity ocx account resume google-antigravity @@ -795,6 +820,15 @@ Use `ocx provider add mine --adapter openai-chat --base-url https://example.com/ ### Cached quota history +Automatic OpenAI account exhaustion checks distinguish purchased usage credits from reset +tickets. Spending remains off by default: enable **Use credits** for that account (stored in +`creditCodexAccountIds`). Only that opt-in together with a fresh positive spendable balance or +unlimited credits can keep an account eligible after included usage reaches 100%; an upstream refusal or overage limit still +blocks that evidence. Credit evidence expires after five minutes, and usage headers do not +renew its clock. `pause-exhausted` uses this rule. Reset tickets alone never grant automatic +headroom, and a credits-only response cannot clear a request cooldown. The default-on main-account +hard lock remains a separate local policy and is not lifted by the credits switch. + `ocx account history openai [--limit 1-200] [--json]` reads stored observations without contacting the provider. The output separates actual observation time, WHAM or response-header source, window family and usage percentage. At most 200 observations per account are retained for 30 days, with global storage bounds. Ordinary token refresh preserves history. Reauthentication, removal or account replacement retires the old publication. Native main and probes performed before a login is published are not included. Missing history means insufficient observations, not zero usage. This command does not spend quota. Effective estimates, when supported by observations, carry the limitations below. diff --git a/docs-site/src/content/docs/reference/configuration/providers.md b/docs-site/src/content/docs/reference/configuration/providers.md index 04eafa67712..729d00601ff 100644 --- a/docs-site/src/content/docs/reference/configuration/providers.md +++ b/docs-site/src/content/docs/reference/configuration/providers.md @@ -225,6 +225,8 @@ Providers can expose a built-in shorthand, such as `agy` for `google-antigravity | `chatServiceTier?` | `boolean` | Provider-wide Chat-wire opt-in for forwarding caller `service_tier` values. On a classified route it governs foreign values such as `flex`, not proxy-owned canonical Fast after capability validation; on an unclassified route it governs every caller value because no Fast capability has been validated. Exact model capability does not authorize foreign forwarding. Responses routes retain their capability-based caller forwarding behavior. | | `promptCacheKey?` | `boolean` | Provider-wide `openai-chat` opt-in for forwarding a `prompt_cache_key`. The adapter forwards the key it is given and never invents one, but the key is not always the caller's: Claude Messages translation derives one from `metadata.user_id`, or from a model/system/tools cohort when no metadata is sent. Default off. Enable only when the upstream documents support, because strict gateways may reject the unknown field with HTTP 400. | | `preserveResponsesReasoningContent?` | `boolean` | Keep plaintext reasoning content on replayed Responses reasoning items instead of blanking it (blanking is the ChatGPT backend's rule). Enable for upstreams whose contract accepts reasoning replay, such as DeepSeek. Proxy-minted `ocxr1` envelopes are always stripped. | +| `preserveResponsesInputItemIds?` | `boolean` | Default off. Preserve valid `input[].id` when `store: false` for explicitly trusted launcher relays that require turn identity. Ordinary Responses providers keep stripping IDs; xAI custom-call ID repair remains active. | +| `preserveResponsesMessageMetadata?` | `boolean` | Default off. Preserve private `internal_chat_message_metadata_passthrough` on input items for explicitly trusted launcher relays. This forwards private turn/session metadata to that destination; do not enable for ordinary gateways. | | `dropResponsesReasoningItems?` | `boolean` | Remove replayed Responses `reasoning` items from continuation input entirely, instead of forwarding them with blanked content. This is lossy: summaries, item ids, and `encrypted_content` go with them, so no reasoning item from the previous turn remains in the forwarded input. It does not remove `previous_response_id`, so an upstream holding its own turn state may still reach it. Enable only for an upstream that rejects reasoning replay outright — the built-in Volcengine Ark Coding Plan preset sets it because validated tool continuations answered `400 InvalidParameter` otherwise. Set it to `false` on that provider to forward reasoning items again. | | `disabled?` | `boolean` | Keep the provider on disk but exclude it from routing and model/catalog listings. | | `apiKey?` | `string` | API key, an `${ENV_VAR}` / `$ENV_VAR` reference, or a `keychain:` reference written by `ocx provider keychain store`. References resolve at request time. See [Storing keys in the OS keychain](#storing-keys-in-the-os-keychain). | @@ -247,6 +249,7 @@ Providers can expose a built-in shorthand, such as `agy` for `google-antigravity | `modelMaxOutputTokens?` | `Record` | Positive per-model `openai-chat` fallback budgets; exact/pattern matches beat the provider default. | | `modelCosts?` | `Record` | Per-model display prices (USD per 1M tokens), keyed by that provider's exact upstream model id — not a provider identifier or a routed `provider/model` label, e.g. `{ "deepseek-v4-flash": { "input": 0.14, "output": 0.28, "cacheRead": 0.0028, "cacheWrite": 0 } }`. Any model id is a valid key — custom providers may target any OpenAI-compatible endpoint through the `openai-chat` adapter, and local or internal provider ids work even when they are absent from the built-in catalogs. User-configured prices win over the built-in catalogs in the Logs `~$` and Usage estimates; historical entries are repriced from the current overlay, so editing a price can move past totals. The fallback order is user `modelCosts` → exact official correction → jawcode catalog → expected-price overlay → model-level vendor fallback, and an explicit all-zero user entry means a known-zero estimate; delete that model entry to restore automatic pricing. All-zero catalog metadata still falls through. Each rate must be a non-negative finite number at most 1,000,000 (USD per 1M tokens); out-of-range rows are rejected by the management boundary and dropped on load. Display-time estimation only: overlays never affect routing, account selection, quotas, or billing. | | `headers?` | `Record` | Extra upstream headers. Authorization, cookies, API-key headers, embedded newlines, and invalid names are rejected. Requests that ride the provider outbound wrapper — model discovery, connection tests, and other proxy-originated diagnostics such as the Ollama show probe — send `User-Agent: opencodex` unless a User-Agent is set here or by the provider preset. | +| `forwardClientHeaders?` | `string[]` | `openai-responses` only: opt in to copying named inbound client metadata headers onto inference requests when the provider has not already set that header. Credential and transport-owned names such as `Authorization`, cookies, API-key headers, `Content-Type`, `Content-Length`, `Host`, and `x-oai-attestation` are rejected and also filtered at runtime. Header names are case-insensitive and may not repeat. `headers` remains authoritative; the existing caller `User-Agent` fallback still applies when neither surface sets one. Supported names: `originator`, `x-client-request-id`, `x-codex-app-version`, `user-agent`; all other names are refused. | | `openRouterRouting?` | `OpenRouterProviderRouting` | Default OpenRouter `order`, `only`, and `allowFallbacks` preferences; valid only for canonical OpenRouter with `openai-chat`. | | `modelOpenRouterRouting?` | `Record` | Exact model-id overrides that replace the provider-wide OpenRouter preference. | | `vercelGatewayRouting?` | `VercelGatewayRouting` | Default Vercel AI Gateway `order`, `only`, and `sort` (`"cost"` \| `"ttft"` \| `"tps"`) preferences; valid only for canonical Vercel AI Gateway with `openai-chat`. | diff --git a/docs-site/src/content/docs/reference/configuration/routing.md b/docs-site/src/content/docs/reference/configuration/routing.md index bce76e5594a..1b7c6d1de8f 100644 --- a/docs-site/src/content/docs/reference/configuration/routing.md +++ b/docs-site/src/content/docs/reference/configuration/routing.md @@ -182,7 +182,10 @@ only currently eligible members of `targets`; missing, failed, or invalid decisi eligible member, while caller cancellation remains terminal. Adding the provider or Combo never changes `defaultProvider` or hides direct model rows. See [Decision method](/guides/combos/#decision-method) for the three methods, setup, privacy bounds, and -the one-decision-per-call contract. +the one-decision-per-call contract. API-key provider/model scopes also apply to TypeSafe and +self-hosted decision destinations, before their credentials or request state are read. If the key +denies the optional decision destination, no decision request is sent; the ordinary eligible +inference fallback still has to satisfy that key's scope. ### Self-hosted decision model (e.g. Ollama tev1) diff --git a/docs-site/src/content/docs/reference/configuration/server.md b/docs-site/src/content/docs/reference/configuration/server.md index e9fc0cde0ca..e5ba4e2dc33 100644 --- a/docs-site/src/content/docs/reference/configuration/server.md +++ b/docs-site/src/content/docs/reference/configuration/server.md @@ -29,7 +29,7 @@ runs helper features around provider requests. | `usageLedgerMaxBytes?` | `number` | unset | Opt-in ceiling in bytes for `usage.jsonl`. Absent means the request history grows without limit, which stays the default. See [usage history size](#usage-history-size). | | `appOwnedMemoryBudgetMb?` | `number` | `256` | Cap in MiB for evictable app-owned logs, caches, blobs, and continuation payloads. Range 64–4096; not an RSS cap. | | `metricsExport.enabled?` | `boolean` | `false` | Enable process-local aggregate request metrics at authenticated `GET /api/metrics`. Restart required; disabled mode returns 404 and starts no exporter activity. | -| `spend?` | `{ root?: { maxTokens?: number }; identity?: { maxTokens?: number }; pool?: { maxTokens?: number }; retentionDays?: number }` | unset | Durable token ceilings, off unless you write one. Each scope bounds settled spend plus in-flight reservations plus unresolved spend: `root` is one task including its whole fan-out, `identity` is one account across every task it serves, and `pool` is one provider pool. They intersect, so a request is admitted only when all three have room — which is what holds a ceiling against a client that mints a new task id per request. A reservation is the request's whole input plus its enforceable output ceiling, counted as if every cached prefix misses. Observe-only mode still journals, so every server owns the state directory's single-writer lease; an explicit sibling must use a separate `OPENCODEX_HOME`. Spend survives an ordinary process restart when its writes reached the filesystem, but the journal does not promise survival across host power loss because each append is not fsynced. Raising or removing the value is what grants more. `maxTokens` must be a positive integer (0 would refuse everything), `retentionDays` is 1–365 and defaults to 7, and an unknown key in this section is rejected rather than ignored. A refusal is a local HTTP 429 carrying `x-opencodex-local-refusal: workflow_spend_exhausted`, and its message names the scope and the ceiling; no provider is contacted. | +| `spend?` | `{ root?: { maxTokens?: number }; identity?: { maxTokens?: number }; pool?: { maxTokens?: number }; retentionDays?: number }` | unset | Durable token ceilings, off unless you write one. Each scope bounds settled spend plus in-flight reservations plus unresolved spend: `root` is one task including its whole fan-out, `identity` is one account across every task it serves, and `pool` is one provider pool. They intersect, so a request is admitted only when all three have room — which is what holds a ceiling against a client that mints a new task id per request. A reservation is the request's whole input plus its enforceable output ceiling, counted as if every cached prefix misses. Observe-only mode still journals, so every server owns the state directory's single-writer lease; an explicit sibling must use a separate `OPENCODEX_HOME`. Spend survives an ordinary process restart when its writes reached the filesystem, but the journal does not promise survival across host power loss because each append is not fsynced. Raising or removing the value is what grants more. `maxTokens` must be a positive integer (0 would refuse everything), `retentionDays` is 1–365 and defaults to 7, and an unknown key in this section is rejected rather than ignored. A refusal is a local HTTP 429 carrying `x-opencodex-local-refusal: workflow_spend_exhausted`, and its message names the scope and the ceiling; no provider is contacted. With an applicable ceiling, dispatch is also refused if its token reservation cannot be booked, including full tracking capacity. Requests without an applicable ceiling remain observe-only. | | `codexAutoStart?` | `boolean` | `true` | Let the Codex shim run `ocx ensure` before launching Codex. False makes ensure a no-op. | | `codexShimAutoRestore?` | `boolean` | `true` | Restore an installed shim after a completed external Codex update replaces it. Environment opt-out: `OPENCODEX_CODEX_SHIM_AUTO_RESTORE=0`. | @@ -79,6 +79,12 @@ refusal as rate-limit or quota evidence against the credential it was holding. T requests such as vision and web search are replayed normally, because repeating them cannot duplicate a turn. +Native ChatGPT Responses and compact HTTP requests whose serialized JSON strings are at least +1 MiB in UTF-8 use byte-buffer uploads to avoid Bun resetting a large string upload before response +headers arrive. This preserves the request +contents and does not enable automatic retries. A genuine connection reset still follows the +replay-refusal policy above. + A native Responses provider can opt into replacing that send with [`retryOnReset`](/reference/configuration/providers/#provider-entries-ocxproviderconfig). The same grant covers the case where the connection survives the header and the SSE body then dies carrying only control @@ -156,7 +162,17 @@ only these fields when comparing network modes, rather than the full account lis | `internal_error` | An internal refresh step failed. | Only `http_error` includes `httpStatus`. Other statuses do not imply HTTP 0 or an -account entitlement problem. +account entitlement problem. An `http_error` may also include `code` when the provider +named a reason that only a new sign-in fixes (`token_invalidated`, `invalid_refresh_token`, +`invalid_workspace_selected`). A `token_invalidated` response indicates a revoked session; +the row then shows `needsReauth: true` with the last-known plan. + +If the token endpoint rejects the stored main refresh grant, automatic requests stop retrying +that same grant even if a usage refresh succeeds. Sign in again with `codex login` to replace +the credential. The process keeps up to 64 profile-and-grant refusal records; restarting the +proxy or evicting an old record permits a fresh check. Rate-limit and server failures remain +retryable and do not retire the grant. Credentials supplied by the caller remain caller-owned. + ### Which proxy path is used? @@ -642,8 +658,10 @@ subscription with a warning when detection is inconclusive. See ## Compaction routing In **Dashboard → Overview → Compaction routing**, choose a model, which triggers it applies to, -and an optional reasoning effort, then click **Save**. Select **Use conversation model** and save -to remove the override. Changes apply to the next compaction request without restarting the proxy. +an optional reasoning effort, and whether the override covers all source models or only selected +sources. For selected sources, check individual models or provider-wide `provider/*` entries, +then click **Save**. Select **Use conversation model** and save to remove the override. Changes +apply to the next compaction request without restarting the proxy. Set `compactionRouting` in OpenCodex `config.json` to override the model Codex's compaction requests use. The setting is disabled when omitted. @@ -659,6 +677,17 @@ requests use. The setting is disabled when omitted. ``` `model` accepts native model IDs, provider-qualified model IDs, and configured combos. +Optional `sourceModels` limits the override to exact incoming model IDs or `provider/*` patterns, +for example `["kimi/*", "google-antigravity/*"]`. Matching is case-sensitive. Omit it for the +existing all-model behavior; empty, duplicate, or malformed lists are invalid. The Dashboard +source-scope picker can edit the allowlist and preserves saved selectors absent from the current +catalog. The model checklist shows at most 300 matching entries; use its search field to narrow +large catalogs without removing saved selections. You can also edit it through `config.json` or `PUT /api/settings`. +This checks the incoming compaction model, not an inferred conversation model. If Codex sends a +bare native compaction model for a routed thread, it does not match those provider patterns and +keeps its existing route. This conservative behavior protects GPT but can leave some automatic +compactions on the native path. Ordinary non-compaction turns are never changed. + `reasoningEffort` is optional; omit it to preserve the incoming effort. Supported declarations are `none`, `minimal`, `low`, `medium`, `high`, `xhigh`, `max`, and `ultra`. Existing provider effort rules still apply. The native `/responses/compact` endpoint keeps @@ -675,7 +704,9 @@ the canonical OpenAI quota is exhausted. Codex picks a bare native model for the turn, and OpenCodex reserves that for an enabled canonical `openai` provider whenever one exists, so the compaction fails with a quota error before the routed turn begins even though the thread itself runs elsewhere. Naming `"auto"` here points the compaction at a -provider-qualified model with its own credentials and quota. +provider-qualified model with its own credentials and quota only when `sourceModels` is omitted +or matches the incoming model. A list containing only `provider/*` entries does not cover a bare +native compaction model. OpenCodex changes only requests with explicit `request_kind: "compaction"` metadata whose `compaction.trigger` is one of the values you listed, sent to `/v1/responses/compact` or to @@ -686,8 +717,8 @@ metadata are supplied they must name the same trigger. WebSocket requests use ea metadata rather than the connection's earlier handshake metadata. The selected model's provider receives the entire conversation for summarization, including -conversations that normally run on another provider. A combo selector sends it to every combo -target, including failover targets. The dashboard panel states this next to the model picker +conversations that normally run on another provider. A combo selects a target by its routing strategy and may retry the same conversation on another +target after a retryable failure, so one or more targets can receive it. The dashboard panel states this next to the model picker and names the destination provider, or the combo's target providers, once a model is chosen. When the override covers automatic compaction, that transfer happens without you asking for it, at whatever point Codex decides to compact; the dashboard panel says so as well. @@ -804,6 +835,8 @@ Explicit selection fails closed when the provider is missing, disabled, incompat usable key; it never falls back to another paid upstream. The endpoint must implement the OpenAI Images API paths and response shape expected by Codex. +Both search loops keep `web_search` declared after the search budget is exhausted. Further calls receive a paired limit-reached result without another physical search; at most `maxSearchesPerTurn + 3` model iterations run before a terminal error. Ordinary caller tools and cancellation still end the loop. Empty-answer recovery alone removes all tools. + ### `webSearchSidecar` (`OcxWebSearchSidecarConfig`) RunTurn adapters also use the configured search sidecar. Search turns preserve progress heartbeats. A first-event OAuth 429 rotates the account on the initial request and on each post-search answer request. The replay keeps the search tool and the gathered results. With `emptyCompletionRetry: true`, an empty answer before the search limit receives one retry using the current conversation and gathered results. The existing tool-free recovery after the search limit remains available independently of that setting. Upstream failures stop the turn instead of triggering another search. @@ -894,3 +927,15 @@ WebSocket control paths. See the canonical guide for [supported steering routes and settings](/guides/codex-integration/#steering-continuation-settings), [typed result and approval continuations](/guides/codex-integration/#rich-tool-results-and-explicit-approvals-after-response-completion), and [confirmation deadlines and retained context](/guides/codex-integration/#steering-confirmation-deadlines-and-retained-context). + +### Forced Claude Code subagent model + +The Subagents page offers **Force all subagents onto one model**, off by default. Select an exposed roster-style id, such as `combo/tev-auto`, then enable the switch. The roster is offered first; unavailable saved roster entries cannot be force targets. + +`ocx agent subagents force combo/tev-auto` sets `claudeCode.subagentModelForce`; `ocx agent subagents force -` clears it. `ocx agent status` reports the setting. `GET /api/subagent-models` returns `force`, `forceAvailable`, and `forceStatus`; `PUT` accepts `{ "force": "combo/tev-auto" }` or `{ "force": null }` without changing the roster. Omitting `force` leaves it unchanged. Invalid or unexposed targets are rejected on write; stale targets are reported and skipped at launch. + +This takes effect on the **next routed `ocx claude` launch**, injecting `CLAUDE_CODE_SUBAGENT_MODEL` as an explicit proxy alias (with `[1m]` only for an authoritative million-token window; native Claude targets use a reversible native alias) and `CLAUDE_CODE_SUBAGENT_MODEL_FORCE=1`. Each nonempty shell-exported variable independently wins. Native launches inject neither variable; plain `claude` is not affected. No plugin files or `settings.json` are modified by this setting. + +Claude Code **2.1.257 or newer** is required for FORCE. Plugin and built-in agents (including Explore/Plan) and per-call model arguments are overridden. Forks and subagent skills with `model: inherit` keep the main conversation model. The main loop and Haiku/small-fast sidecars are unaffected. Existing roster files remain available. + +The dashboard warns about old or unknown CLI versions, unavailable targets, and either variable already present in `settings.json` → `env` (which overrides launch env). Detection is read-only and server-local: it cannot inspect another launch shell, another machine, or project-local settings. An unknown result is not proof of force support. diff --git a/docs-site/src/content/docs/reference/management-api.md b/docs-site/src/content/docs/reference/management-api.md index 2692bae66fb..1378a13f20e 100644 --- a/docs-site/src/content/docs/reference/management-api.md +++ b/docs-site/src/content/docs/reference/management-api.md @@ -756,3 +756,15 @@ Anthropic OAuth only; `{ provider: "anthropic", accountId, threshold }` accepts Account-list DTOs include `autoSwitchThresholdOverride` (integer/null), `autoSwitchThreshold` (pool default), and `effectiveAutoSwitchThreshold`. 0 disables usage-driven switching only; it never disables pause or 429 recovery. HTTP: 400 invalid/unsupported; 404 missing account; `oauth_mutation_busy` on lock contention. + +### Forced Claude Code subagent model + +The Subagents page offers **Force all subagents onto one model**, off by default. Select an exposed roster-style id, such as `combo/tev-auto`, then enable the switch. The roster is offered first; unavailable saved roster entries cannot be force targets. + +`ocx agent subagents force combo/tev-auto` sets `claudeCode.subagentModelForce`; `ocx agent subagents force -` clears it. `ocx agent status` reports the setting. `GET /api/subagent-models` returns `force`, `forceAvailable`, and `forceStatus`; `PUT` accepts `{ "force": "combo/tev-auto" }` or `{ "force": null }` without changing the roster. Omitting `force` leaves it unchanged. Invalid or unexposed targets are rejected on write; stale targets are reported and skipped at launch. + +This takes effect on the **next routed `ocx claude` launch**, injecting `CLAUDE_CODE_SUBAGENT_MODEL` as an explicit proxy alias (with `[1m]` only for an authoritative million-token window; native Claude targets use a reversible native alias) and `CLAUDE_CODE_SUBAGENT_MODEL_FORCE=1`. Each nonempty shell-exported variable independently wins. Native launches inject neither variable; plain `claude` is not affected. No plugin files or `settings.json` are modified by this setting. + +Claude Code **2.1.257 or newer** is required for FORCE. Plugin and built-in agents (including Explore/Plan) and per-call model arguments are overridden. Forks and subagent skills with `model: inherit` keep the main conversation model. The main loop and Haiku/small-fast sidecars are unaffected. Existing roster files remain available. + +The dashboard warns about old or unknown CLI versions, unavailable targets, and either variable already present in `settings.json` → `env` (which overrides launch env). Detection is read-only and server-local: it cannot inspect another launch shell, another machine, or project-local settings. An unknown result is not proof of force support. diff --git a/docs-site/src/content/docs/reference/proxy-formats.md b/docs-site/src/content/docs/reference/proxy-formats.md index bad29c7e258..818d6a8fa47 100644 --- a/docs-site/src/content/docs/reference/proxy-formats.md +++ b/docs-site/src/content/docs/reference/proxy-formats.md @@ -248,6 +248,10 @@ buffer budget, the relay reports the failure without waiting for upstream inspec to finish. It cancels the upstream work and emits `response.failed` followed by `data: [DONE]`; a budget overflow uses the `translation_buffer_limit` error code. +Responses SSE accepts CR, LF and CRLF line endings, including mixed endings and CRLF split across +network chunks. Tool validation, Chat translation, terminal inspection and WebSocket projection +use the same event boundaries. A CR-only completion does not wait for the upstream connection to close. + Client-facing Responses SSE frames are limited to 4 MiB per frame, measured in raw bytes before the SSE block delimiter. On HTTP, an unterminated upstream frame that exceeds the limit fails closed with a synthetic `response.failed` event followed by `data: [DONE]`. On the Responses WebSocket diff --git a/docs-site/src/content/docs/ru/guides/claude-code.md b/docs-site/src/content/docs/ru/guides/claude-code.md index 7f0ef98641b..a98843179bf 100644 --- a/docs-site/src/content/docs/ru/guides/claude-code.md +++ b/docs-site/src/content/docs/ru/guides/claude-code.md @@ -635,3 +635,17 @@ Responses `web_search_call` в парные блоки Anthropic `server_tool_us Параметр `claudeCode.stabilizePromptCache: true` в `config.json` переносит поддерживаемые уведомления Claude в конце системных инструкций в последнее пользовательское сообщение на маршрутах с преобразованием. По умолчанию он выключен (`false`). Включайте его только когда такое изменение роли допустимо для ваших клиентов. Примеры в блоках кода и нераспознанный текст сохраняются; нативная передача Anthropic не меняется. Без метаданных ключ кэша рассчитывается по стабилизированным инструкциям. Идентификатор разговора не создаётся, попадания в кэш не гарантируются. На всех преобразованных Chat-маршрутах напоминания в истории сохраняют свою позицию в разговоре — после ожидаемых результатов инструментов. Поэтому добавление нового напоминания не переписывает начальный системный промпт, а инструкция из середины разговора не оказывается раньше тех ходов, после которых она была написана. Роль этой позиции определяется отдельно: напоминание отправляется как `system`, если провайдер не записал `foldDeveloperRoleToSystem: false` — эта запись означает, что вышестоящий сервис принимает роль `developer`, и тогда она передаётся в той же позиции. Сервис, который её не принимает, отвечает `400 role 'developer' is not allowed`, и ход не начинается, поэтому незаписанное назначение сворачивается. Это работает независимо от `stabilizePromptCache`; нативная передача Anthropic остаётся прежней. Для повторного использования кэша по-прежнему нужны стабильный идентификатор сессии и доступный кэш провайдера. Изменения прежних инструкций или инструментов и сжатие разговора также могут влиять на попадания в кэш; само сохранение порядка напоминаний не гарантирует повторного использования. + +### Forced Claude Code subagent model + +The Subagents page offers **Force all subagents onto one model**, off by default. Select an exposed roster-style id, such as `combo/tev-auto`, then enable the switch. The roster is offered first; unavailable saved roster entries cannot be force targets. + +`ocx agent subagents force combo/tev-auto` sets `claudeCode.subagentModelForce`; `ocx agent subagents force -` clears it. `ocx agent status` reports the setting. `GET /api/subagent-models` returns `force`, `forceAvailable`, and `forceStatus`; `PUT` accepts `{ "force": "combo/tev-auto" }` or `{ "force": null }` without changing the roster. Omitting `force` leaves it unchanged. Invalid or unexposed targets are rejected on write; stale targets are reported and skipped at launch. + +This takes effect on the **next routed `ocx claude` launch**, injecting `CLAUDE_CODE_SUBAGENT_MODEL` as an explicit proxy alias (with `[1m]` only for an authoritative million-token window; native Claude targets use a reversible native alias) and `CLAUDE_CODE_SUBAGENT_MODEL_FORCE=1`. Each nonempty shell-exported variable independently wins. Native launches inject neither variable; plain `claude` is not affected. No plugin files or `settings.json` are modified by this setting. + +Claude Code **2.1.257 or newer** is required for FORCE. Plugin and built-in agents (including Explore/Plan) and per-call model arguments are overridden. Forks and subagent skills with `model: inherit` keep the main conversation model. The main loop and Haiku/small-fast sidecars are unaffected. Existing roster files remain available. + +The dashboard warns about old or unknown CLI versions, unavailable targets, and either variable already present in `settings.json` → `env` (which overrides launch env). Detection is read-only and server-local: it cannot inspect another launch shell, another machine, or project-local settings. An unknown result is not proof of force support. + +Explicit gateway selectors on a generated agent request take precedence over its legacy `ocx-route` fallback, even if the saved force setting changes after launch. For shell or settings overrides of generated roster agents, use an explicit gateway alias; bare Claude ids retain the older-client fallback behavior. Native aliases restore their bare model before the existing credential and model-map checks. Connected launches validate force targets against a fresh authenticated gateway catalog; failed discovery skips automatic force injection, and cached context windows alone never prove availability. diff --git a/docs-site/src/content/docs/ru/guides/combos.md b/docs-site/src/content/docs/ru/guides/combos.md index 48b4977abf0..fe966d0cbf9 100644 --- a/docs-site/src/content/docs/ru/guides/combos.md +++ b/docs-site/src/content/docs/ru/guides/combos.md @@ -162,6 +162,7 @@ ocx combo set balanced \ | HTTP 410 с явным признаком окончания срока службы модели, retirement, deprecated, sunset, decommissioned или недоступности | Перевести только эту цель в cooldown и перейти дальше. Несвязанные ответы 410 остаются terminal-ошибками. | | Классифицированная ошибка аутентификации, подписки, квоты, rate-limit, перегрузки или upstream-server | Перевести цель в cooldown и переключиться, даже если одного статуса недостаточно. | | Отмена клиентом (499), `origin_rejected`, отказ из-за cyber-policy, переполнение контекста или иной некорректный запрос | Остановиться и вернуть ошибку; другая цель не сделает такой запрос корректным. | +| Точный отказ HTTP 400 о том, что модель не поддерживается в Codex с аккаунтом ChatGPT | До начала вывода пробует следующую настроенную цель. Полная фраза отказа должна находиться в `detail`, `error.message` или обычном тексте; смешанные объекты `detail` и `error` завершают запрос. Сам этот отказ не переводит аккаунт в состояние повторного входа и не включает период ожидания для всех моделей провайдера. | | Структурированный HTTP 400, отклоняющий необязательный `user`, неподдерживаемое значение `reasoning.effort`/`reasoning_effort` или специфичный для модели отказ входного изображения (`param: input`) | До начала вывода переходит к следующей допустимой цели без охлаждения; см. «Совместимость необязательных параметров» ниже. | | Первый вызов инструмента в Responses-ходе внутрипроцессного адаптера (`runTurn`), который текущий запрос не объявлял, до любого вывода и до побочного эффекта, небезопасного для повтора | Охлаждает цель и переходит к следующей с тем же каталогом инструментов. После видимого вывода или небезопасного для повтора побочного эффекта отказ окончателен. Запросы Chat Completions и Anthropic Messages не меняются. | | Любая другая неклассифицированная ошибка | Остановиться и вернуть ошибку. | diff --git a/docs-site/src/content/docs/ru/guides/integrations.md b/docs-site/src/content/docs/ru/guides/integrations.md index d6ae86fa896..09080b48fca 100644 --- a/docs-site/src/content/docs/ru/guides/integrations.md +++ b/docs-site/src/content/docs/ru/guides/integrations.md @@ -48,12 +48,37 @@ modelProfile: Модели GJC с поддерживаемыми уровнями усилия рассуждения экспортируют `reasoning: true`, `thinking.levels` и `compat.supportsReasoningEffort`, чтобы в GJC можно было выбрать уровень. Нативные модели Codex получают стандартные уровни, даже если каталог их не указывает. Без известных уровней эти поля не выводятся. `none` не отправляет уровень, а `ultra` при отправке становится `max`, поэтому их не предлагают. Обновите интеграцию, чтобы обновить параметры моделей. Управляемой интеграции OpenCode принадлежат два фрагмента: `provider.opencodex` -(opencode V1) и `providers.opencodex` (opencode V2). Только блок V2 содержит -варианты effort для каждой модели, поэтому оба фрагмента записываются и +(opencode V1) и `providers.opencodex` (opencode V2). Оба блока несут одни и те же +объявленные варианты effort и значения по умолчанию для каждой модели: в устаревшем +блоке это значение по умолчанию в options модели и карта вариантов, в блоке V2 — +settings модели и собственный массив вариантов, поэтому оба фрагмента записываются и поддерживаются синхронно. Они называют одни и те же id провайдера и моделей, а opencode V2 объединяет их в одну запись провайдера. Apply, Refresh, Disable и Restore действуют на оба фрагмента; другие ваши провайдеры, агенты, -сочетания клавиш и записи MCP остаются без изменений. +сочетания клавиш и записи MCP остаются без изменений. Оба фрагмента строятся из +фактических канонических метаданных моделей — как и экспорт Kilo и запускатель +`ocx opencode`. Явное пользовательское переопределение всегда выигрывает, а +очищенная лестница остаётся пустой; лестница или значение по умолчанию никогда +не выдумываются для модели, которая их не объявляет. Достоверный лимит контекста +переносится вместе с лимитом выхода: известное значение выхода — явное или из +метаданных каталога — выигрывает, иначе применяется обязательный по схеме запасной +лимит 32000, ограниченный контекстом; без достоверного контекста весь блок limit +опускается. Возможности, лестницы effort, значения по умолчанию и необязательный +лимит входа записываются только известными. Собственный объект capabilities блока V2 +требует известного значения tools, поэтому при неизвестной поддержке инструментов он +опускается целиком, а не частично; объявления модальностей в устаревшем блоке +остаются. Вместо выдуманной лестницы пишутся только элементы управления: блок V2 +всегда несёт явный массив вариантов — пустой, если ничего не объявлено, потому что +его отсутствие заставило бы OpenCode синтезировать low/medium/high, — а модель с +известной фиксированной рассуждающей способностью, но без регулируемой лестницы +получает +вместо effort только неактивное подавление: и устаревший блок OpenCode, и Kilo отключают +каждый id ступени, который клиент иначе сгенерировал бы. Ни один из них не добавляет +выбираемый effort. +Выбираемые варианты могут переопределить значение effort по +умолчанию — включая `none`, и только если лестница самой модели его объявляет. +Что бы ни выбрал клиент, закреплённая политика effort на прокси +по-прежнему применяется к уходящему запросу. Управляемая поддержка DSH требует как минимум **DSH 0.1.0-rc.6**. OpenCodex владеет только `llm-pi-ai.providers.opencodex`: Apply и Refresh заменяют этот @@ -304,8 +329,11 @@ OMP поддерживает заголовки уровня провайдер **При `ocx opencode` побеждают блоки провайдера, заданные запускателем.** Запускатель внедряет `provider.opencodex` и `providers.opencodex` через `OPENCODE_CONFIG_CONTENT`, что имеет приоритет над такими же записями на диске. -Остальная конфигурация opencode действует как обычно. Переключатель на этой -странице важен при прямом запуске `opencode`. +Остальная конфигурация opencode действует как обычно. Эти блоки строятся из тех +же фактических канонических метаданных моделей, что и экспорты, поэтому +запускатель и управляемая интеграция описывают одни и те же модели, лимиты и +варианты effort. Переключатель на этой странице важен при прямом запуске +`opencode`. ## Из терминала @@ -339,10 +367,11 @@ ocx mcode ``` После подключения `ocx sync` и `POST /api/sync` обновляют принадлежащие -интеграции каталоги MCode, Pi, Aside, Raycast и omo с текущим выбором моделей, -контекстными окнами и ступенями effort. Запуск прокси обновляет принадлежащий -ему каталог Raycast. Изменения видимости моделей, выбора провайдера или пресетов -также обновляют подключённые каталоги Pi, Aside, Raycast и omo. +интеграции каталоги MCode, Pi, Aside, Raycast, omo, OpenCode и Kilo с текущим +выбором моделей, контекстными окнами и ступенями effort. Запуск прокси обновляет +принадлежащий ему каталог Raycast. Изменения видимости моделей, выбора провайдера +или пресетов также обновляют подключённые каталоги Pi, Aside, Raycast, omo, +OpenCode и Kilo. Отсутствующие, изменённые извне или небезопасные блоки остаются нетронутыми, как и ранее принадлежавшие интеграции блоки, удалённые вами вручную. Есть исключение для включённого профиля Aside: если каталог его аккаунта @@ -351,8 +380,8 @@ ocx mcode такое поведение для всех зарегистрированных профилей. Синхронизация не создаёт отсутствующие каталоги аккаунтов и не заменяет ручные блоки. Отказ или пересечение операций обновления сообщается отдельно для каждого -клиента. Чтобы загрузить новый файл, начните новую сессию Pi или полностью -закройте и откройте Aside. +клиента. Чтобы загрузить новый файл, начните новую сессию Pi, OpenCode или Kilo +либо полностью закройте и откройте Aside. Обновление Aside требует [совместимого работающего прокси](#управление-профилями-aside). Если Models показывает **“Model selection saved”** вместе с предупреждением об @@ -538,9 +567,9 @@ Kilo CLI, VS Code и JetBrains используют общую глобальн Kilo объединяет все эти глобальные файлы. Если другой файл-кандидат тоже определяет `provider.opencodex`, статус перечисляет конфликтующие файлы, а применение и замена отклоняются. Перед включением удалите `provider.opencodex` из этих файлов. Отключение уже принадлежащего OpenCodex блока доступно и при таком конфликте. Нечитаемый или небезопасный файл-кандидат также блокирует запись. -Интеграции принадлежит только `provider.opencodex` в формате OpenCode V1 (`npm`, `options`, `models`). Поле OpenCode V2 `providers` не создаётся. `$schema`, `model`, `enabled_providers`, MCP и прочие ключи остаются под управлением пользователя. После применения выберите в Kilo `opencodex/`. +Интеграции принадлежит только `provider.opencodex` в формате OpenCode V1 (`npm`, `options`, `models`). Поле OpenCode V2 `providers` не создаётся; вместо этого варианты effort для каждой модели записываются в блоке провайдера как карта вариантов — из тех же фактических канонических метаданных, что и экспорт OpenCode. `$schema`, `model`, `enabled_providers`, MCP и прочие ключи остаются под управлением пользователя. После применения выберите в Kilo `opencodex/`. -Для loopback значение `options.apiKey` — `{env:OPENCODEX_KILO_API_KEY}`. При привязке не к loopback авторизация переносится в `options.headers["x-opencodex-api-key"]`; настоящий ключ не записывается. Применение переписывает весь глобальный файл как форматированный JSON, поэтому комментарии и завершающие запятые в других ключах не сохраняются. Kilo не участвует в автоматическом обновлении каталога; после изменения выбора маршрутизируемых моделей обновите интеграцию явно. +Для loopback значение `options.apiKey` — `{env:OPENCODEX_KILO_API_KEY}`. При привязке не к loopback авторизация переносится в `options.headers["x-opencodex-api-key"]`; настоящий ключ не записывается. Применение переписывает весь глобальный файл как форматированный JSON, поэтому комментарии и завершающие запятые в других ключах не сохраняются. Kilo входит в автоматическое обновление каталога: `ocx sync`, `POST /api/sync` и изменения видимости, провайдера или пресетов обновляют принадлежащий блок Kilo по тем же безопасным правилам «только свои блоки», что и остальные клиенты, а новая сессия Kilo загружает обновлённый файл. ```bash ocx integration client enable --client kilo diff --git a/docs-site/src/content/docs/ru/guides/remote-link.md b/docs-site/src/content/docs/ru/guides/remote-link.md index c53f8b63a5f..b88687261d5 100644 --- a/docs-site/src/content/docs/ru/guides/remote-link.md +++ b/docs-site/src/content/docs/ru/guides/remote-link.md @@ -11,7 +11,7 @@ description: Подключите компьютер OpenCodex Home к комп - Для связи, инициированной со стороны Child, Child должен входить на Home по ключу OpenSSH (вход по паролю не поддерживается). - На Child установлен OpenCodex 2.66.0 или новее (для связи со стороны Child — и на Home). - Оба компьютера работают под macOS или Linux. -- Панель, с которой начинают связь, открыта на самом этом компьютере (браузер или настольное приложение, автономная установка) или через сопряжённую сессию Hub. +- Панель для добавления Child со стороны Home открыта на Home или через сопряжённую сессию Hub. Чтобы превратить текущий компьютер в Child, нужна сессия панели, сопряжённая оператором; локальная сессия без учётных данных не может подтвердить это изменение маршрутизации. SSH с паролем и Windows сейчас не поддерживаются. Связь можно начать с любой стороны: с Home, как описано ниже, или с Child, как описано в разделе «Подключение этого компьютера как Child». @@ -25,6 +25,17 @@ SSH с паролем и Windows сейчас не поддерживаются. Панель не просит вводить токен. Сначала выполняется проверка хоста, и применить связь можно только после явного подтверждения отпечатка. +Окно добавления Child поясняет, что Child использует провайдеры этого Home через SSH, и показывает требования выше. Псевдонимы берутся из `~/.ssh/config` на Home, где работает OpenCodex, а не обязательно на компьютере с браузером. Если поиск успешен, но хостов нет, добавьте запись `Host`, как ниже, и повторите поиск. Существующий псевдоним можно ввести вручную; выбор или ввод включает проверку соединения. В окне есть ссылка на это руководство. + +```sshconfig +Host devbox + HostName devbox.example.com + User you + IdentityFile ~/.ssh/id_ed25519 +``` + +При ошибке поиска окно показывает сбой загрузки, доступную причину и повторную попытку, а не пустой список. Причина ошибки проверки соединения и очищенная подсказка SSH остаются только в активном окне, без дублирования позади него. Проверьте причину и при необходимости диагностику SSH ниже, затем повторите попытку. Повторный поиск сохраняет введённый псевдоним, но требует новой проверки отпечатка перед подключением. + ## Подключение этого компьютера как Child На компьютере, который должен использовать провайдеров Home: @@ -36,7 +47,9 @@ SSH с паролем и Windows сейчас не поддерживаются. При подключении OpenCodex на этом компьютере перезапустится. Уже идущие запросы Codex сначала завершатся, а новые запросы могут не проходить до минуты, пока идёт перезапуск. Затем панель перезагрузится сама и покажет связь Child. Codex продолжит использовать `http://127.0.0.1:/v1` на этом компьютере без токена и без переменных окружения: локальный OpenCodex передаёт каждый запрос на Home, а Home обслуживает его своими провайдерами и учётными записями. -Роль **Child** доступна, только пока OpenCodex работает на настроенном порту, потому что Child перезапускается именно на нём. Если панель сообщает, что OpenCodex работает не на настроенном порту, сначала перезапустите его на этом порту. +Если **Child** просит сначала выполнить сопряжение этого компьютера, откройте на нём HTTP-панель по настроенному IP-адресу обратной петли, например `http://127.0.0.1:`. Локальная форма сопряжения появляется только при отсутствии сопряжения, когда панель и API используют один и тот же origin обратной петли. Скопируйте из формы команду `ocx gui pair --origin "http://127.0.0.1:"`, выполните её в терминале на этом компьютере и вставьте одноразовый код в форму. Используйте точно тот origin, который показан в форме; API-ключ провайдера или токен администратора не является кодом сопряжения. Отсутствие сопряжения и несовпадение настроенного порта — разные причины. + +Роль **Child** также требует, чтобы OpenCodex работал в автономном режиме на настроенном порту, потому что Child перезапускается именно на нём. Если панель сообщает, что OpenCodex работает не на настроенном порту, сначала перезапустите его на этом порту. ## Состояние связи diff --git a/docs-site/src/content/docs/ru/reference/cli/agents.md b/docs-site/src/content/docs/ru/reference/cli/agents.md index c1835afff88..3241b5be341 100644 --- a/docs-site/src/content/docs/ru/reference/cli/agents.md +++ b/docs-site/src/content/docs/ru/reference/cli/agents.md @@ -284,3 +284,15 @@ ocx system codex-cli-update attest --candidate --npm-prefix ` преобразуется в `.`: для `*.local` прямое соединение получают `foo.local` и само имя `local`, но не `xlocal`. Точные диапазоны `169.254/16`, `169.254.0.0/16` и `fe80::/10` пропускаются с диагностикой: link-local IP-адреса используют прокси. IP-адреса и `*` принимаются; прочие CIDR, glob-шаблоны и исключения простых имён отменяют обнаружение без изменения окружения. PAC/WPAD, настройки только SOCKS и изменения во время работы автоматически не учитываются. Успех через TUN сам по себе не подтверждает исправность пути HTTP-прокси. См. [команды и состояния на английском](/reference/configuration/server/#codex-quota-network-diagnostics). `dropCodexSafetyBuffering`: не меняет проверки безопасности провайдера или отказы. Native WebSocket `codex.response.metadata.headers` и `/responses/compact` не входят в область фильтра. + +### Forced Claude Code subagent model + +The Subagents page offers **Force all subagents onto one model**, off by default. Select an exposed roster-style id, such as `combo/tev-auto`, then enable the switch. The roster is offered first; unavailable saved roster entries cannot be force targets. + +`ocx agent subagents force combo/tev-auto` sets `claudeCode.subagentModelForce`; `ocx agent subagents force -` clears it. `ocx agent status` reports the setting. `GET /api/subagent-models` returns `force`, `forceAvailable`, and `forceStatus`; `PUT` accepts `{ "force": "combo/tev-auto" }` or `{ "force": null }` without changing the roster. Omitting `force` leaves it unchanged. Invalid or unexposed targets are rejected on write; stale targets are reported and skipped at launch. + +This takes effect on the **next routed `ocx claude` launch**, injecting `CLAUDE_CODE_SUBAGENT_MODEL` as an explicit proxy alias (with `[1m]` only for an authoritative million-token window; native Claude targets use a reversible native alias) and `CLAUDE_CODE_SUBAGENT_MODEL_FORCE=1`. Each nonempty shell-exported variable independently wins. Native launches inject neither variable; plain `claude` is not affected. No plugin files or `settings.json` are modified by this setting. + +Claude Code **2.1.257 or newer** is required for FORCE. Plugin and built-in agents (including Explore/Plan) and per-call model arguments are overridden. Forks and subagent skills with `model: inherit` keep the main conversation model. The main loop and Haiku/small-fast sidecars are unaffected. Existing roster files remain available. + +The dashboard warns about old or unknown CLI versions, unavailable targets, and either variable already present in `settings.json` → `env` (which overrides launch env). Detection is read-only and server-local: it cannot inspect another launch shell, another machine, or project-local settings. An unknown result is not proof of force support. + +## Резервирование токенов и лимиты + +Если к запросу применяется `spend.root.maxTokens`, `spend.identity.maxTokens` или `spend.pool.maxTokens`, отправка отклоняется, когда резерв токенов нельзя учесть, в том числе из-за заполнения хранилища отслеживаемых записей. Запросы без применимого лимита остаются в режиме наблюдения. 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 b7f827b36b6..a3f4c294220 100644 --- a/docs-site/src/content/docs/ru/reference/management-api.md +++ b/docs-site/src/content/docs/ru/reference/management-api.md @@ -412,3 +412,15 @@ fail closed, пока аккаунт отсутствует, а при повт DTO содержит `autoSwitchThresholdOverride` (целое/null), `autoSwitchThreshold` (порог пула) и `effectiveAutoSwitchThreshold`. 0 отключает только переключение по использованию; пауза и восстановление после 429 сохраняются. HTTP: 400 invalid/unsupported; 404 missing account; `oauth_mutation_busy` on lock contention. + +### Forced Claude Code subagent model + +The Subagents page offers **Force all subagents onto one model**, off by default. Select an exposed roster-style id, such as `combo/tev-auto`, then enable the switch. The roster is offered first; unavailable saved roster entries cannot be force targets. + +`ocx agent subagents force combo/tev-auto` sets `claudeCode.subagentModelForce`; `ocx agent subagents force -` clears it. `ocx agent status` reports the setting. `GET /api/subagent-models` returns `force`, `forceAvailable`, and `forceStatus`; `PUT` accepts `{ "force": "combo/tev-auto" }` or `{ "force": null }` without changing the roster. Omitting `force` leaves it unchanged. Invalid or unexposed targets are rejected on write; stale targets are reported and skipped at launch. + +This takes effect on the **next routed `ocx claude` launch**, injecting `CLAUDE_CODE_SUBAGENT_MODEL` as an explicit proxy alias (with `[1m]` only for an authoritative million-token window; native Claude targets use a reversible native alias) and `CLAUDE_CODE_SUBAGENT_MODEL_FORCE=1`. Each nonempty shell-exported variable independently wins. Native launches inject neither variable; plain `claude` is not affected. No plugin files or `settings.json` are modified by this setting. + +Claude Code **2.1.257 or newer** is required for FORCE. Plugin and built-in agents (including Explore/Plan) and per-call model arguments are overridden. Forks and subagent skills with `model: inherit` keep the main conversation model. The main loop and Haiku/small-fast sidecars are unaffected. Existing roster files remain available. + +The dashboard warns about old or unknown CLI versions, unavailable targets, and either variable already present in `settings.json` → `env` (which overrides launch env). Detection is read-only and server-local: it cannot inspect another launch shell, another machine, or project-local settings. An unknown result is not proof of force support. diff --git a/docs-site/src/content/docs/tr/guides/claude-code.md b/docs-site/src/content/docs/tr/guides/claude-code.md index 07fa51bcdbb..648c04377fe 100644 --- a/docs-site/src/content/docs/tr/guides/claude-code.md +++ b/docs-site/src/content/docs/tr/guides/claude-code.md @@ -860,3 +860,17 @@ Dönüştürülen tüm Chat rotalarında zaman çizelgesi hatırlatmaları, bekl ### `anthropicAccountPool.routes` Havuz açıkken `anthropicAccountPool.routes` kuralları ilk eşleşen model için ilk seçimi ve 429 yeniden denemelerini kayıtlı hesaplarla sınırlar. Uygun hesap yoksa istek yerel olarak reddedilir; `fallback: true` normal havuza izin verir. Kurallar model erişimini kanıtlamaz. + +### Forced Claude Code subagent model + +The Subagents page offers **Force all subagents onto one model**, off by default. Select an exposed roster-style id, such as `combo/tev-auto`, then enable the switch. The roster is offered first; unavailable saved roster entries cannot be force targets. + +`ocx agent subagents force combo/tev-auto` sets `claudeCode.subagentModelForce`; `ocx agent subagents force -` clears it. `ocx agent status` reports the setting. `GET /api/subagent-models` returns `force`, `forceAvailable`, and `forceStatus`; `PUT` accepts `{ "force": "combo/tev-auto" }` or `{ "force": null }` without changing the roster. Omitting `force` leaves it unchanged. Invalid or unexposed targets are rejected on write; stale targets are reported and skipped at launch. + +This takes effect on the **next routed `ocx claude` launch**, injecting `CLAUDE_CODE_SUBAGENT_MODEL` as an explicit proxy alias (with `[1m]` only for an authoritative million-token window; native Claude targets use a reversible native alias) and `CLAUDE_CODE_SUBAGENT_MODEL_FORCE=1`. Each nonempty shell-exported variable independently wins. Native launches inject neither variable; plain `claude` is not affected. No plugin files or `settings.json` are modified by this setting. + +Claude Code **2.1.257 or newer** is required for FORCE. Plugin and built-in agents (including Explore/Plan) and per-call model arguments are overridden. Forks and subagent skills with `model: inherit` keep the main conversation model. The main loop and Haiku/small-fast sidecars are unaffected. Existing roster files remain available. + +The dashboard warns about old or unknown CLI versions, unavailable targets, and either variable already present in `settings.json` → `env` (which overrides launch env). Detection is read-only and server-local: it cannot inspect another launch shell, another machine, or project-local settings. An unknown result is not proof of force support. + +Explicit gateway selectors on a generated agent request take precedence over its legacy `ocx-route` fallback, even if the saved force setting changes after launch. For shell or settings overrides of generated roster agents, use an explicit gateway alias; bare Claude ids retain the older-client fallback behavior. Native aliases restore their bare model before the existing credential and model-map checks. Connected launches validate force targets against a fresh authenticated gateway catalog; failed discovery skips automatic force injection, and cached context windows alone never prove availability. diff --git a/docs-site/src/content/docs/tr/guides/integrations.md b/docs-site/src/content/docs/tr/guides/integrations.md index 7d1731ca433..cf0b3872d37 100644 --- a/docs-site/src/content/docs/tr/guides/integrations.md +++ b/docs-site/src/content/docs/tr/guides/integrations.md @@ -223,7 +223,10 @@ asla gerçek bir kimlik bilgisi yazılmaz. **`ocx opencode` için başlatıcının sağlayıcı bloğu kazanır.** Bu başlatıcı, `provider.opencodex`'i diskteki aynı girdiden üstün olan `OPENCODE_CONFIG_CONTENT` aracılığıyla enjekte eder — opencode yapılandırmanızın -geri kalanı her zamanki gibi geçerli olmaya devam eder. Buradaki anahtar, +geri kalanı her zamanki gibi geçerli olmaya devam eder. Başlatıcının yazdığı bloklar, +dışa aktarımlarla aynı geçerli kanonik model meta verisinden (bilinen yetenekler ve +akıl yürütme seçenekleri) kurulur; bilinmeyen değerler uydurulmaz, yalnızca çıkış sınırı +bilinmiyorsa şemanın zorunlu 32000 yedeğine (bağlama göre kırpılmış) düşer. Buradaki anahtar, `opencode`'u doğrudan başlattığınızda önemlidir. ## Terminalden @@ -256,11 +259,12 @@ ocx mcode ``` Bağlandıktan sonra `ocx sync` ve `POST /api/sync`, yönetilen MCode, Pi, Aside, -Raycast ve omo kataloglarını yeniler. Proxy başlangıcı da yönetilen Raycast +Raycast, omo, OpenCode ve Kilo kataloglarını yeniler. Proxy başlangıcı da yönetilen Raycast kataloğunu yeniler. Model görünürlüğü, sağlayıcı veya ön ayar değişiklikleri Pi, -Aside, Raycast ve omo kataloglarını günceller. Eksik, dışarıdan düzenlenmiş, güvenli olmayan +Aside, Raycast, omo, OpenCode ve Kilo kataloglarını günceller. Eksik, dışarıdan düzenlenmiş, güvenli olmayan veya elle kaldırılmış bloklara dokunmaz; yeniden bağlamak istediğinizde -entegrasyonu açıkça etkinleştirin. +entegrasyonu açıkça etkinleştirin. Güncellenen dosyayı yüklemek için yeni bir +Pi, OpenCode veya Kilo oturumu başlatın. Ayrı MiniMax platform CLI'si (`mmx`) bir dosya anahtarı entegrasyonu değildir. Metin komutları MiniMax'ın Anthropic uyumlu uç noktasını kullandığı için OpenCodex, @@ -329,7 +333,7 @@ ocx integration client restore --op ## Kilo -Kilo yalnızca `~/.config/kilo` altındaki ilk mevcut genel dosyada `provider.opencodex` yazar (`XDG_CONFIG_HOME` bu dizini taşır; hiçbir aday yoksa `kilo.jsonc` oluşturulur). Başka bir aday dosya da `provider.opencodex` tanımlıyorsa durum çakışma bildirir ve Uygula işlemi reddedilir. Diğer anahtarlar değişmez. Uygula dosyanın tamamını yeniden yazar; yorumlar ve sondaki virgüller korunmaz. Kilo’da `opencodex/` seçin. +Kilo yalnızca `~/.config/kilo` altındaki ilk mevcut genel dosyada `provider.opencodex` yazar (`XDG_CONFIG_HOME` bu dizini taşır; hiçbir aday yoksa `kilo.jsonc` oluşturulur). Başka bir aday dosya da `provider.opencodex` tanımlıyorsa durum çakışma bildirir ve Uygula işlemi reddedilir. Diğer anahtarlar değişmez. Modellerin akıl yürütme seçenekleri, sağlayıcı bloğu içinde bir varyant haritası olarak OpenCode dışa aktarımıyla aynı geçerli kanonik meta veriden yazılır. `ocx sync` ve `POST /api/sync` OpenCodex'e ait Kilo bloğunu yeniler; güncellenen dosyayı yeni bir Kilo oturumu yükler. Uygula dosyanın tamamını yeniden yazar; yorumlar ve sondaki virgüller korunmaz. Kilo’da `opencodex/` seçin. Başka bir aday çakışsa veya ayrıştırılamasa bile Devre Dışı Bırak, kaydedilen dosyadaki OpenCodex'e ait bloğu kaldırabilir; diğer aday dosya değişmez. diff --git a/docs-site/src/content/docs/tr/guides/remote-link.md b/docs-site/src/content/docs/tr/guides/remote-link.md index 3fe13ce1e89..9a866de7bd4 100644 --- a/docs-site/src/content/docs/tr/guides/remote-link.md +++ b/docs-site/src/content/docs/tr/guides/remote-link.md @@ -11,7 +11,7 @@ Makine bağlantısı, bir OpenCodex **Home** bilgisayarını bir **Child** bilgi - Child tarafından başlatılan bağlantı için Child, Home bilgisayarına OpenSSH anahtarıyla giriş yapabilmelidir (parola girişi desteklenmez). - Child bilgisayarında OpenCodex 2.66.0 veya sonrası kuruludur (Child tarafından başlatılan bağlantıda Home üzerinde de). - Her iki bilgisayar da macOS veya Linux çalıştırır. -- Bağlantıyı başlatan kontrol paneli o bilgisayarın kendisinde (bağımsız kurulumda tarayıcı veya masaüstü uygulaması) ya da eşleştirilmiş bir Hub oturumu üzerinden açılır. +- Home üzerinden Child ekleyen kontrol paneli Home üzerinde ya da eşleştirilmiş bir Hub oturumunda açılır. Geçerli bilgisayarı Child'a dönüştürmek için operatörün eşleştirdiği bir kontrol paneli oturumu gerekir; kimlik bilgisi olmadan oluşturulan yerel oturum bu yönlendirme değişikliğini onaylayamaz. Parolalı SSH ve Windows mevcut akışın dışındadır. Bağlantı iki taraftan da başlatılabilir: aşağıda anlatıldığı gibi Home tarafından ya da "Bu bilgisayarı Child olarak bağlama" bölümünde anlatıldığı gibi Child tarafından. @@ -25,6 +25,17 @@ Parolalı SSH ve Windows mevcut akışın dışındadır. Bağlantı iki tarafta Kontrol paneli belirteç girmenizi istemez. Önce ana bilgisayarı yoklar ve parmak izini açıkça onaylamadan bağlantıyı uygulamaz. +Child ekleme penceresi, Child’ın bu Home’un sağlayıcılarını SSH üzerinden kullandığını ve yukarıdaki gereksinimleri açıklar. Takma adlar, tarayıcıyı gösteren bilgisayardan değil, OpenCodex’i çalıştıran Home üzerindeki `~/.ssh/config` dosyasından gelir. Tarama başarılı olup hiç ana bilgisayar bulunamazsa aşağıdaki gibi bir `Host` girdisi ekleyip yeniden tarayın. Mevcut bir takma adı elle de girebilirsiniz; seçim veya giriş bağlantı testini etkinleştirir. Pencerede bu kılavuza bağlantı bulunur. + +```sshconfig +Host devbox + HostName devbox.example.com + User you + IdentityFile ~/.ssh/id_ed25519 +``` + +Tarama başarısız olursa boş liste yerine yükleme hatası, mevcut neden ve yeniden deneme eylemi gösterilir. Bağlantı testinin nedeni ve temizlenmiş SSH ipucu yalnızca etkin pencerede görünür, arkasında yinelenmez. Nedeni ve gerektiğinde aşağıdaki SSH tanılamasını kontrol edip yeniden deneyin. Yeniden tarama girilen takma adı korur ancak bağlantıdan önce parmak izinin yeniden incelenmesini gerektirir. + ## Bu bilgisayarı Child olarak bağlama Home'un sağlayıcılarını kullanacak bilgisayarda: @@ -36,7 +47,9 @@ Home'un sağlayıcılarını kullanacak bilgisayarda: Bağlanmak bu bilgisayardaki OpenCodex'i yeniden başlatır. Zaten çalışan Codex istekleri önce tamamlanır ve yeniden başlatma sırasında yeni istekler bir dakikaya kadar başarısız olabilir. Ardından kontrol paneli kendiliğinden yeniden yüklenir ve Child bağlantısını gösterir. Codex bu bilgisayarda `http://127.0.0.1:/v1` adresini kullanmaya devam eder ve belirteç veya ortam değişkeni ayarlamanız gerekmez: yerel OpenCodex her isteği Home'a aktarır, Home da kendi sağlayıcıları ve hesaplarıyla yanıt verir. -**Child** rolü yalnızca OpenCodex yapılandırılmış bağlantı noktasında çalışırken kullanılabilir, çünkü Child tam olarak o bağlantı noktasında yeniden başlar. Kontrol paneli OpenCodex'in yapılandırılmış bağlantı noktasında çalışmadığını söylerse önce onu o bağlantı noktasında yeniden başlatın. +**Child** önce bu bilgisayarı eşleştirmenizi istiyorsa bu bilgisayarın yapılandırılmış HTTP geri döngü IP adresindeki kontrol panelini açın; örneğin `http://127.0.0.1:`. Yerel eşleştirme formu yalnızca eşleştirme eksik olduğunda ve kontrol paneli ile API aynı geri döngü kaynağını kullandığında görünür. Formdaki `ocx gui pair --origin "http://127.0.0.1:"` komutunu kopyalayıp bu bilgisayarın terminalinde çalıştırın, ardından tek kullanımlık kodu forma yapıştırın. Formda gösterilen kaynak adresini aynen kullanın; sağlayıcı API anahtarı veya yönetici belirteci eşleştirme kodu değildir. Eksik eşleştirme ile yapılandırılmış bağlantı noktası uyuşmazlığı farklı nedenlerdir. + +**Child** rolü ayrıca OpenCodex'in bağımsız çalışma modunda, yapılandırılmış bağlantı noktasında çalışmasını gerektirir, çünkü Child tam olarak o bağlantı noktasında yeniden başlar. Kontrol paneli OpenCodex'in yapılandırılmış bağlantı noktasında çalışmadığını söylerse önce onu o bağlantı noktasında yeniden başlatın. ## Bağlantı durumu diff --git a/docs-site/src/content/docs/tr/reference/cli/agents.md b/docs-site/src/content/docs/tr/reference/cli/agents.md index 5a610d045d3..c54835a4c00 100644 --- a/docs-site/src/content/docs/tr/reference/cli/agents.md +++ b/docs-site/src/content/docs/tr/reference/cli/agents.md @@ -335,3 +335,15 @@ Kimlik veya özet yalnızca gözlem anındaki dosyaları tanımlar; kalıcı gü Doğrulanmış OpenCodex yapılandırmasını inceleyin ve güvenle değiştirin. `show` ve `get` sırları maskeler. İçe aktarma yazmadan önce doğrular ve `--yes` gerektirir. + +### Forced Claude Code subagent model + +The Subagents page offers **Force all subagents onto one model**, off by default. Select an exposed roster-style id, such as `combo/tev-auto`, then enable the switch. The roster is offered first; unavailable saved roster entries cannot be force targets. + +`ocx agent subagents force combo/tev-auto` sets `claudeCode.subagentModelForce`; `ocx agent subagents force -` clears it. `ocx agent status` reports the setting. `GET /api/subagent-models` returns `force`, `forceAvailable`, and `forceStatus`; `PUT` accepts `{ "force": "combo/tev-auto" }` or `{ "force": null }` without changing the roster. Omitting `force` leaves it unchanged. Invalid or unexposed targets are rejected on write; stale targets are reported and skipped at launch. + +This takes effect on the **next routed `ocx claude` launch**, injecting `CLAUDE_CODE_SUBAGENT_MODEL` as an explicit proxy alias (with `[1m]` only for an authoritative million-token window; native Claude targets use a reversible native alias) and `CLAUDE_CODE_SUBAGENT_MODEL_FORCE=1`. Each nonempty shell-exported variable independently wins. Native launches inject neither variable; plain `claude` is not affected. No plugin files or `settings.json` are modified by this setting. + +Claude Code **2.1.257 or newer** is required for FORCE. Plugin and built-in agents (including Explore/Plan) and per-call model arguments are overridden. Forks and subagent skills with `model: inherit` keep the main conversation model. The main loop and Haiku/small-fast sidecars are unaffected. Existing roster files remain available. + +The dashboard warns about old or unknown CLI versions, unavailable targets, and either variable already present in `settings.json` → `env` (which overrides launch env). Detection is read-only and server-local: it cannot inspect another launch shell, another machine, or project-local settings. An unknown result is not proof of force support. diff --git a/docs-site/src/content/docs/tr/reference/configuration/server.md b/docs-site/src/content/docs/tr/reference/configuration/server.md index 301faadfe16..2fedcb91188 100644 --- a/docs-site/src/content/docs/tr/reference/configuration/server.md +++ b/docs-site/src/content/docs/tr/reference/configuration/server.md @@ -305,3 +305,15 @@ yeniden kullanır. Hedeflenen hesap ve iş yükünü kapsamlı bir şekilde test ## Codex kota ağı tanılaması Ana Codex hesabının satırındaki `quotaRefresh`, kalan kotayı veya model erişim yetkisini değil, kota sorgusunun sonucunu açıklar. Önbellek kullanıldığında ya da sorgu yapılmadığında alan bulunmayabilir. Sorgu, etkileşimli terminalin değil çalışan proxy servisinin ortamını kullanır. `proxy` ayarlanmazsa mevcut ortam korunur; `"auto"` başlangıçta Windows veya macOS statik HTTP/HTTPS ayarlarını okur. macOS üzerinde devralınmış proxy varsa bu ayarlar okunmaz. macOS üzerinde geçerli `*.` kalıbı `.` olur: `*.local` için `foo.local` ve yalın `local` doğrudan gider, `xlocal` gitmez. Tam `169.254/16`, `169.254.0.0/16` ve `fe80::/10` aralıkları bir tanıyla atlanır; link-local IP adresleri proxy kullanır. IP adresleri ve `*` kabul edilir; diğer CIDR, glob ve yalın ana makine istisnaları ortam değiştirilmeden keşfi reddeder. PAC/WPAD, yalnızca SOCKS ayarları ve çalışma sırasındaki değişiklikler otomatik uygulanmaz. TUN ile başarı, HTTP proxy yolunun da çalıştığını tek başına göstermez. [Komutlar ve durumlar için İngilizce bölüme](/reference/configuration/server/#codex-quota-network-diagnostics) bakın. + +### Forced Claude Code subagent model + +The Subagents page offers **Force all subagents onto one model**, off by default. Select an exposed roster-style id, such as `combo/tev-auto`, then enable the switch. The roster is offered first; unavailable saved roster entries cannot be force targets. + +`ocx agent subagents force combo/tev-auto` sets `claudeCode.subagentModelForce`; `ocx agent subagents force -` clears it. `ocx agent status` reports the setting. `GET /api/subagent-models` returns `force`, `forceAvailable`, and `forceStatus`; `PUT` accepts `{ "force": "combo/tev-auto" }` or `{ "force": null }` without changing the roster. Omitting `force` leaves it unchanged. Invalid or unexposed targets are rejected on write; stale targets are reported and skipped at launch. + +This takes effect on the **next routed `ocx claude` launch**, injecting `CLAUDE_CODE_SUBAGENT_MODEL` as an explicit proxy alias (with `[1m]` only for an authoritative million-token window; native Claude targets use a reversible native alias) and `CLAUDE_CODE_SUBAGENT_MODEL_FORCE=1`. Each nonempty shell-exported variable independently wins. Native launches inject neither variable; plain `claude` is not affected. No plugin files or `settings.json` are modified by this setting. + +Claude Code **2.1.257 or newer** is required for FORCE. Plugin and built-in agents (including Explore/Plan) and per-call model arguments are overridden. Forks and subagent skills with `model: inherit` keep the main conversation model. The main loop and Haiku/small-fast sidecars are unaffected. Existing roster files remain available. + +The dashboard warns about old or unknown CLI versions, unavailable targets, and either variable already present in `settings.json` → `env` (which overrides launch env). Detection is read-only and server-local: it cannot inspect another launch shell, another machine, or project-local settings. An unknown result is not proof of force support. 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 1666fb0a8de..a2e1ad81366 100644 --- a/docs-site/src/content/docs/tr/reference/management-api.md +++ b/docs-site/src/content/docs/tr/reference/management-api.md @@ -448,3 +448,15 @@ Yalnızca Anthropic OAuth. `{ provider: "anthropic", accountId, threshold }`: 0 DTO: `autoSwitchThresholdOverride` (tam sayı/null), `autoSwitchThreshold` (havuz varsayılanı), `effectiveAutoSwitchThreshold`. 0 yalnızca kullanıma dayalı geçişi kapatır; duraklatma ve 429 kurtarması sürer. HTTP: 400 invalid/unsupported; 404 missing account; `oauth_mutation_busy` on lock contention. + +### Forced Claude Code subagent model + +The Subagents page offers **Force all subagents onto one model**, off by default. Select an exposed roster-style id, such as `combo/tev-auto`, then enable the switch. The roster is offered first; unavailable saved roster entries cannot be force targets. + +`ocx agent subagents force combo/tev-auto` sets `claudeCode.subagentModelForce`; `ocx agent subagents force -` clears it. `ocx agent status` reports the setting. `GET /api/subagent-models` returns `force`, `forceAvailable`, and `forceStatus`; `PUT` accepts `{ "force": "combo/tev-auto" }` or `{ "force": null }` without changing the roster. Omitting `force` leaves it unchanged. Invalid or unexposed targets are rejected on write; stale targets are reported and skipped at launch. + +This takes effect on the **next routed `ocx claude` launch**, injecting `CLAUDE_CODE_SUBAGENT_MODEL` as an explicit proxy alias (with `[1m]` only for an authoritative million-token window; native Claude targets use a reversible native alias) and `CLAUDE_CODE_SUBAGENT_MODEL_FORCE=1`. Each nonempty shell-exported variable independently wins. Native launches inject neither variable; plain `claude` is not affected. No plugin files or `settings.json` are modified by this setting. + +Claude Code **2.1.257 or newer** is required for FORCE. Plugin and built-in agents (including Explore/Plan) and per-call model arguments are overridden. Forks and subagent skills with `model: inherit` keep the main conversation model. The main loop and Haiku/small-fast sidecars are unaffected. Existing roster files remain available. + +The dashboard warns about old or unknown CLI versions, unavailable targets, and either variable already present in `settings.json` → `env` (which overrides launch env). Detection is read-only and server-local: it cannot inspect another launch shell, another machine, or project-local settings. An unknown result is not proof of force support. diff --git a/docs-site/src/content/docs/zh-cn/guides/claude-code.md b/docs-site/src/content/docs/zh-cn/guides/claude-code.md index efd959d4cc5..010bbab0068 100644 --- a/docs-site/src/content/docs/zh-cn/guides/claude-code.md +++ b/docs-site/src/content/docs/zh-cn/guides/claude-code.md @@ -569,3 +569,17 @@ Claude 模型时自动加载。对于原生透传,这是正常现象;对于 ### 第一方模型选择器的上下文标记 对于权威上下文窗口至少为一百万 token 的路由模型,Desktop Code 标签页的模型选择器会添加 `[1m]`,使 Claude 按 1M 窗口计量,而不是使用自定义模型较小的默认窗口。标签、配置中的顺序和提供方路由保持不变。窗口未知或小于一百万 token 的模型(包括原生模型的大窗口选项)不添加标记,因为选择器无法保证 Desktop 或远程运行器收到配套的压缩环境设置。`ocx claude` 的自动上下文与压缩配套设置保持不变。现有对话会保留已保存的选择器,直到你在刷新后的模型选择器中重新选择该模型。 + +### Forced Claude Code subagent model + +The Subagents page offers **Force all subagents onto one model**, off by default. Select an exposed roster-style id, such as `combo/tev-auto`, then enable the switch. The roster is offered first; unavailable saved roster entries cannot be force targets. + +`ocx agent subagents force combo/tev-auto` sets `claudeCode.subagentModelForce`; `ocx agent subagents force -` clears it. `ocx agent status` reports the setting. `GET /api/subagent-models` returns `force`, `forceAvailable`, and `forceStatus`; `PUT` accepts `{ "force": "combo/tev-auto" }` or `{ "force": null }` without changing the roster. Omitting `force` leaves it unchanged. Invalid or unexposed targets are rejected on write; stale targets are reported and skipped at launch. + +This takes effect on the **next routed `ocx claude` launch**, injecting `CLAUDE_CODE_SUBAGENT_MODEL` as an explicit proxy alias (with `[1m]` only for an authoritative million-token window; native Claude targets use a reversible native alias) and `CLAUDE_CODE_SUBAGENT_MODEL_FORCE=1`. Each nonempty shell-exported variable independently wins. Native launches inject neither variable; plain `claude` is not affected. No plugin files or `settings.json` are modified by this setting. + +Claude Code **2.1.257 or newer** is required for FORCE. Plugin and built-in agents (including Explore/Plan) and per-call model arguments are overridden. Forks and subagent skills with `model: inherit` keep the main conversation model. The main loop and Haiku/small-fast sidecars are unaffected. Existing roster files remain available. + +The dashboard warns about old or unknown CLI versions, unavailable targets, and either variable already present in `settings.json` → `env` (which overrides launch env). Detection is read-only and server-local: it cannot inspect another launch shell, another machine, or project-local settings. An unknown result is not proof of force support. + +Explicit gateway selectors on a generated agent request take precedence over its legacy `ocx-route` fallback, even if the saved force setting changes after launch. For shell or settings overrides of generated roster agents, use an explicit gateway alias; bare Claude ids retain the older-client fallback behavior. Native aliases restore their bare model before the existing credential and model-map checks. Connected launches validate force targets against a fresh authenticated gateway catalog; failed discovery skips automatic force injection, and cached context windows alone never prove availability. diff --git a/docs-site/src/content/docs/zh-cn/guides/combos.md b/docs-site/src/content/docs/zh-cn/guides/combos.md index 24b1ae889cc..d0513216b58 100644 --- a/docs-site/src/content/docs/zh-cn/guides/combos.md +++ b/docs-site/src/content/docs/zh-cn/guides/combos.md @@ -151,6 +151,7 @@ combo 失败分为 **跳转** 失败和 **终止** 失败。 | HTTP 410,并明确表明模型已到生命周期终点、retired、deprecated、sunset、decommissioned 或不再可用 | 仅冷却该目标并继续跳转。无关的 410 仍然是终止错误。 | | 被分类为认证、订阅、配额、速率限制、过载或上游服务器错误 | 即使仅凭状态码不足以判断,也会使该目标进入冷却并跳转。 | | 客户端取消(499)、`origin_rejected`、cyber-policy 拒绝、上下文溢出,或其他无效请求 | 停止并返回错误;换其他目标也无法让请求变得有效。 | +| HTTP 400 明确表示使用 ChatGPT 账户运行 Codex 时不支持该模型 | 在输出提交前尝试下一个已配置目标。完整拒绝句必须位于 `detail`、`error.message` 或纯文本中;同时包含 `detail` 与 `error` 的混合封装仍会终止请求。此原因本身不会将账户标为需要重新登录,也不会让供应商的所有模型进入冷却。 | | 结构化 HTTP 400,明确拒绝 `user`、对 `reasoning.effort`/`reasoning_effort` 返回不支持值,或返回模型特定图像输入拒绝(`param: input`) | 在输出开始前跳转到下一个符合条件的目标,且不记录冷却时间;参见下方可选参数兼容性。 | | 由进程内适配器(`runTurn`)执行的 Responses 回合中,当前请求未声明的第一个工具调用(在任何输出和不可重放的副作用之前) | 让该目标进入冷却,并以相同的工具目录跳转到下一个目标。出现可见输出或不可重放的副作用之后,拒绝即为最终结果。Chat Completions 和 Anthropic Messages 请求不受影响。 | | 任何其他未分类错误 | 停止并返回错误。 | diff --git a/docs-site/src/content/docs/zh-cn/guides/integrations.md b/docs-site/src/content/docs/zh-cn/guides/integrations.md index 90018e3100a..9510139aae3 100644 --- a/docs-site/src/content/docs/zh-cn/guides/integrations.md +++ b/docs-site/src/content/docs/zh-cn/guides/integrations.md @@ -39,7 +39,7 @@ modelProfile: 具有受支持推理强度梯度的 GJC 模型会导出 `reasoning: true`、`thinking.levels` 和 `compat.supportsReasoningEffort`,让 GJC 提供强度选择。原生 Codex 模型即使未在目录中列出梯度,也会获得标准梯度。没有已知梯度的模型会省略这些字段;`none` 不发送强度,而 `ultra` 在传输时折叠为 `max`,因此不会列为选项。刷新集成即可更新模型选项。 -托管 OpenCode 集成管理两个片段:`provider.opencodex`(opencode V1)和 `providers.opencodex`(opencode V2)。只有 V2 配置块包含各模型的推理强度变体,因此两者都会写入并保持同步;它们使用相同的提供商与模型 id,opencode V2 会将它们合并为一个提供商条目。Apply、Refresh、Disable 和 Restore 都作用于两个片段;其他提供商、代理、快捷键与 MCP 条目保持不变。 +托管 OpenCode 集成管理两个片段:`provider.opencodex`(opencode V1)和 `providers.opencodex`(opencode V2)。两个片段携带相同的各模型已声明推理选择与默认值:旧版块以模型 options 中的默认值加变体映射表达,V2 块以模型 settings 加原生变体数组表达,因此两者都会写入并保持同步;它们使用相同的提供商与模型 id,opencode V2 会将它们合并为一个提供商条目。Apply、Refresh、Disable 和 Restore 都作用于两个片段;其他提供商、代理、快捷键与 MCP 条目保持不变。这两个片段都基于生效的规范模型元数据构建——Kilo 导出和 `ocx opencode` 启动器也是如此。显式自定义覆盖始终优先,被清空的推理阶梯保持为空;不会为未声明阶梯或默认值的模型捏造任何值。携带权威的上下文上限时会同时携带输出上限:已知的输出值(显式或目录元数据)优先,否则使用 schema 必需的 32000 回退值(按上下文截断);没有权威上下文上限时整个 limit 块都会省略。能力、推理阶梯、默认值和可选的输入上限只在已知时写入。V2 块的原生 capabilities 对象需要已知的 tools 值,因此工具支持未知时会整体省略而不写残缺的对象,旧版块中的模态声明保持不变。不捏造阶梯,但仍会写入控制而非选项:V2 块始终携带显式的变体数组——无声明时为空数组,因为省略会让 OpenCode 自行合成 low/medium/high;已知有固定推理但无可调阶梯的模型获得的是仅禁用的抑制而非推理强度:旧版 OpenCode 配置块和 Kilo 都会禁用客户端原本会生成的每一个阶梯 id。两者都不会添加可选的推理强度。可选变体可以覆盖已知时写入的逐模型推理默认值——包括 `none`,且仅当模型自身声明的阶梯包含它时才会提供。无论客户端选择什么,代理面向上游的固定策略仍作用于实际发出的请求。 托管 DSH 支持的最低兼容版本为 **DSH 0.1.0-rc.6**。OpenCodex 只管理 `llm-pi-ai.providers.opencodex`;Apply 和 Refresh 替换该片段,Disable 只移除该片段,Restore 恢复已记录的快照。DSH 会热重载提供商变更。这些操作不会改变用户的默认模型或原生 `deepseek-official` 提供商。托管 DSH 集成目前仅支持回环地址,绝不会写入真实凭据。 @@ -115,7 +115,7 @@ TOML 日期和时间也会拒绝托管重写,因为合并步骤会将这些带 **Kimi Code 不能保存环境变量引用,**因此其配置包含 `opencodex-loopback` 占位符而非密钥。任何客户端配置中都不会写入真实凭据。 -**对于 `ocx opencode`,启动器的提供商配置块优先。** 启动器通过 `OPENCODE_CONFIG_CONTENT` 注入 `provider.opencodex` 和 `providers.opencodex`,其优先级高于磁盘上的相同条目;其余 opencode 配置照常生效。直接启动 `opencode` 时,此处的开关才是关键。 +**对于 `ocx opencode`,启动器的提供商配置块优先。** 启动器通过 `OPENCODE_CONFIG_CONTENT` 注入 `provider.opencodex` 和 `providers.opencodex`,其优先级高于磁盘上的相同条目;其余 opencode 配置照常生效。这些配置块与导出使用相同的生效规范模型元数据构建,因此启动器和托管集成描述的是同一批模型、上限和推理选项。直接启动 `opencode` 时,此处的开关才是关键。 ## 从终端操作 @@ -144,7 +144,7 @@ ocx integration client enable --client mcode ocx mcode ``` -连接后,`ocx sync` 和 `POST /api/sync` 会按当前模型选择、上下文窗口及推理强度级别刷新已管理的 MCode、Pi、Aside、Raycast 和 omo 目录。代理启动时会刷新已管理的 Raycast 目录。模型可见性、提供商选择或预设变化,也会刷新已连接的 Pi、Aside、Raycast 和 omo 目录。缺失、被外部编辑或不安全的配置块不会被触碰;此前归 OpenCodex 管理、但被你手动删除的配置块也不会重建。已启用的 Aside 配置文件是“仅刷新已管理配置块”规则的例外:如果账户目录存在且从未有过已管理配置块,当该位置为空时,同步可以创建首个配置块。此前连接过 Aside 会默认对所有已注册配置文件启用这一行为。同步不会创建缺失的账户目录,也不会替换手动配置块。拒绝或重叠的刷新会按客户端分别报告。启动新 Pi 会话,或完全退出并重新打开 Aside,才能加载更新后的文件。Aside 刷新要求[运行中的兼容代理](#aside-配置文件控制)。 +连接后,`ocx sync` 和 `POST /api/sync` 会按当前模型选择、上下文窗口及推理强度级别刷新已管理的 MCode、Pi、Aside、Raycast、omo、OpenCode 和 Kilo 目录。代理启动时会刷新已管理的 Raycast 目录。模型可见性、提供商选择或预设变化,也会刷新已连接的 Pi、Aside、Raycast、omo、OpenCode 和 Kilo 目录。缺失、被外部编辑或不安全的配置块不会被触碰;此前归 OpenCodex 管理、但被你手动删除的配置块也不会重建。已启用的 Aside 配置文件是“仅刷新已管理配置块”规则的例外:如果账户目录存在且从未有过已管理配置块,当该位置为空时,同步可以创建首个配置块。此前连接过 Aside 会默认对所有已注册配置文件启用这一行为。同步不会创建缺失的账户目录,也不会替换手动配置块。拒绝或重叠的刷新会按客户端分别报告。启动新的 Pi、OpenCode 或 Kilo 会话,或完全退出并重新打开 Aside,才能加载更新后的文件。Aside 刷新要求[运行中的兼容代理](#aside-配置文件控制)。 如果 Models 同时显示 **“Model selection saved”** 和客户端刷新警告,说明选择已保存,但一个或多个客户端文件未能更新。警告会指出受影响的客户端,以及适用时的 Aside 配置文件,并解释拒绝原因。启动新会话前,请打开 **Integrations** 检查该客户端或配置文件。处理报告的问题后,重试 `ocx sync`;重叠操作必须先完成。如果警告包含备份路径,或指出恢复未完成,请在重试前检查恢复状态。仅有选择保存成功的提示,并不能证明客户端文件恢复完成。 @@ -222,9 +222,9 @@ Kilo CLI、VS Code 和 JetBrains 共用一份全局配置。此集成只在 `~/. Kilo 会合并所有这些全局文件。如果另一个候选文件也定义了 `provider.opencodex`,状态会列出冲突文件,应用和替换都会被拒绝。启用集成前,请从那些文件中移除 `provider.opencodex`。即使发生这种冲突,仍可禁用已归 OpenCodex 所有的配置块。无法读取或不安全的候选文件也会阻止写入。 -仅 `provider.opencodex` 属于此集成,采用 OpenCode V1 结构(`npm`、`options`、`models`);不会输出 OpenCode V2 的 `providers` 键。`$schema`、`model`、`enabled_providers`、MCP 等键仍由用户管理。应用后,在 Kilo 中选择 `opencodex/`。 +仅 `provider.opencodex` 属于此集成,采用 OpenCode V1 结构(`npm`、`options`、`models`);不会输出 OpenCode V2 的 `providers` 键。模型级的推理选择会以变体映射的形式写入 provider 配置块,与 OpenCode 导出使用相同的生效规范元数据。`$schema`、`model`、`enabled_providers`、MCP 等键仍由用户管理。应用后,在 Kilo 中选择 `opencodex/`。 -回环连接使用 `{env:OPENCODEX_KILO_API_KEY}` 作为 `options.apiKey`。非回环绑定将认证移到 `options.headers["x-opencodex-api-key"]`,且不会写入真实密钥。应用时会将整个全局文件重写为格式化 JSON,因此其他键中的注释和尾随逗号不会保留。Kilo 不参与自动目录刷新;更改路由模型选择后,请明确刷新此集成。 +回环连接使用 `{env:OPENCODEX_KILO_API_KEY}` 作为 `options.apiKey`。非回环绑定将认证移到 `options.headers["x-opencodex-api-key"]`,且不会写入真实密钥。应用时会将整个全局文件重写为格式化 JSON,因此其他键中的注释和尾随逗号不会保留。Kilo 参与自动目录刷新:`ocx sync`、`POST /api/sync` 以及可见性、提供商或预设变更,会按与其他客户端相同的仅刷新自有配置块的安全规则刷新归 OpenCodex 所有的 Kilo 配置块,新的 Kilo 会话会加载更新后的文件。 ```bash ocx integration client enable --client kilo diff --git a/docs-site/src/content/docs/zh-cn/guides/minimax.md b/docs-site/src/content/docs/zh-cn/guides/minimax.md index 48e633e63e7..4ab74ee3a00 100644 --- a/docs-site/src/content/docs/zh-cn/guides/minimax.md +++ b/docs-site/src/content/docs/zh-cn/guides/minimax.md @@ -10,6 +10,10 @@ MiniMax 发布两种不同的命令行产品。OpenCodex 在它们实际提供 ## MiniMax Code +在 OpenCodex 的模型管理页面,MiniMax Coding Plan 两个区域的 `MiniMax-M3` 和 +`MiniMax-M3.1-Flash-Preview` 均声明支持图片输入。只有所有选定模型均支持图片输入时, +Combo 的“图片 / 多模态”开关才可用。该开关控制图片附件,不提供视频上传能力。 + 先按照 MiniMax 的说明安装并登录 MiniMax Code,然后启动 OpenCodex 并连接可逆的文件集成: ```bash diff --git a/docs-site/src/content/docs/zh-cn/guides/remote-link.md b/docs-site/src/content/docs/zh-cn/guides/remote-link.md index 6b092f1b9ab..a59e2a57fb1 100644 --- a/docs-site/src/content/docs/zh-cn/guides/remote-link.md +++ b/docs-site/src/content/docs/zh-cn/guides/remote-link.md @@ -11,7 +11,7 @@ description: 通过 SSH 将 OpenCodex 主机与子机连接起来。 - 对于由子机发起的链接,子机必须能使用 OpenSSH 密钥登录主机(不支持密码登录)。 - 子机已安装 OpenCodex 2.66.0 或更高版本(由子机发起的链接还要求主机也满足)。 - 两台电脑运行 macOS 或 Linux。 -- 发起链接的控制台需在那台电脑本机打开(独立安装的浏览器或桌面应用),或通过已配对的 Hub 会话打开。 +- 从主机添加子机的控制台需在主机上打开,或使用已配对的 Hub 会话。将当前电脑转换为子机需要操作员配对的控制台会话;无凭据签发的本地会话不能提交这项路由更改。 密码 SSH 和 Windows 不在当前流程中。链接可以从任意一侧发起:按下文从主机发起,或按“将这台电脑连接为子机”一节从子机发起。 @@ -25,6 +25,17 @@ description: 通过 SSH 将 OpenCodex 主机与子机连接起来。 控制台不会要求输入令牌。它会先探测主机,只有明确确认指纹后才能应用链接。 +添加子机窗口会说明子机通过 SSH 使用此主机的提供商,并列出上述前提条件。别名来自运行 OpenCodex 的主机上的 `~/.ssh/config`,不一定是显示浏览器的电脑。如果发现成功但没有主机,请添加如下 `Host` 条目后重新扫描。也可以手动输入已有别名;选择或输入别名后即可启用连接测试。窗口还提供本指南的链接。 + +```sshconfig +Host devbox + HostName devbox.example.com + User you + IdentityFile ~/.ssh/id_ed25519 +``` + +如果发现失败,窗口会显示加载失败、可用的请求原因和重试按钮,而不是声称列表为空。连接测试失败的具体原因和清理后的 SSH 提示只在活动窗口中显示,不会在背后重复显示。检查原因,必要时使用下方 SSH 诊断方法,然后重试。重新扫描会保留输入的别名,但连接前必须重新检查指纹。 + ## 将这台电脑连接为子机 在要使用主机提供商的电脑上: @@ -36,7 +47,9 @@ description: 通过 SSH 将 OpenCodex 主机与子机连接起来。 连接会重启这台电脑上的 OpenCodex。已在运行的 Codex 请求会先完成,重启期间新的请求可能在最多一分钟内失败。随后控制台会自动重新加载并显示子机链接。Codex 继续使用这台电脑上的 `http://127.0.0.1:/v1`,无需设置令牌或环境变量:本地 OpenCodex 会把每个请求转发给主机,由主机用它自己的提供商和账户提供服务。 -只有当 OpenCodex 在其配置的端口上运行时,才能选择 **Child** 角色,因为子机会在正好这个端口上重启。如果控制台提示 OpenCodex 未在其配置的端口上运行,请先在该端口上重启它。 +如果 **Child** 提示先配对这台电脑,请在这台电脑上打开配置的 HTTP 回环 IP 地址控制台,例如 `http://127.0.0.1:`。只有在缺少配对且控制台与 API 使用同一个回环源时,才会显示本地配对表单。复制表单中的 `ocx gui pair --origin "http://127.0.0.1:"` 命令,在这台电脑的终端中运行,再将一次性代码粘贴到表单中。必须使用表单显示的确切源;提供商 API 密钥或管理员令牌不是配对代码。缺少配对与配置端口不匹配是不同的原因。 + +选择 **Child** 角色还要求 OpenCodex 以独立运行模式在其配置的端口上运行,因为子机会在正好这个端口上重启。如果控制台提示 OpenCodex 未在其配置的端口上运行,请先在该端口上重启它。 ## 链接状态 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 5b1a432af82..e2235e720bb 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 @@ -78,6 +78,10 @@ Logs 可组合界面、被拦截请求、提供商、完整模型名、状态、 ## 模型可见性 +开关会立即响应,保存则按点击顺序在后台执行,因此可以连续调整多个模型。队列处理完并与服务器 +核对列表后,才显示保存结果。保存失败会提示错误,并在能读取服务器状态时恢复实际选择。 +请等保存结果出现后再离开模型页或切换服务器;离开时会丢弃尚未发送的排队修改。 + **Models** 开关表示 Codex 中的最终可见状态。路由模型只有在 provider allowlist 中(或未设置 allowlist)且未被禁用时才会开启。开启模型会原子地协调两个过滤条件;**全部开启** 会清除 allowlist,因此以后新发现的模型也会开启。 ### 在提供方工作区管理模型 diff --git a/docs-site/src/content/docs/zh-cn/reference/cli/agents.md b/docs-site/src/content/docs/zh-cn/reference/cli/agents.md index 4c1795216d2..41f690752e9 100644 --- a/docs-site/src/content/docs/zh-cn/reference/cli/agents.md +++ b/docs-site/src/content/docs/zh-cn/reference/cli/agents.md @@ -234,3 +234,15 @@ ocx system codex-cli-update attest --candidate --npm-prefix ...` 检查并安全修改已验证的 OpenCodex 配置。`show` 和 `get` 会隐藏密钥。导入会先验证再写入,并且需要 `--yes`。 + +### Forced Claude Code subagent model + +The Subagents page offers **Force all subagents onto one model**, off by default. Select an exposed roster-style id, such as `combo/tev-auto`, then enable the switch. The roster is offered first; unavailable saved roster entries cannot be force targets. + +`ocx agent subagents force combo/tev-auto` sets `claudeCode.subagentModelForce`; `ocx agent subagents force -` clears it. `ocx agent status` reports the setting. `GET /api/subagent-models` returns `force`, `forceAvailable`, and `forceStatus`; `PUT` accepts `{ "force": "combo/tev-auto" }` or `{ "force": null }` without changing the roster. Omitting `force` leaves it unchanged. Invalid or unexposed targets are rejected on write; stale targets are reported and skipped at launch. + +This takes effect on the **next routed `ocx claude` launch**, injecting `CLAUDE_CODE_SUBAGENT_MODEL` as an explicit proxy alias (with `[1m]` only for an authoritative million-token window; native Claude targets use a reversible native alias) and `CLAUDE_CODE_SUBAGENT_MODEL_FORCE=1`. Each nonempty shell-exported variable independently wins. Native launches inject neither variable; plain `claude` is not affected. No plugin files or `settings.json` are modified by this setting. + +Claude Code **2.1.257 or newer** is required for FORCE. Plugin and built-in agents (including Explore/Plan) and per-call model arguments are overridden. Forks and subagent skills with `model: inherit` keep the main conversation model. The main loop and Haiku/small-fast sidecars are unaffected. Existing roster files remain available. + +The dashboard warns about old or unknown CLI versions, unavailable targets, and either variable already present in `settings.json` → `env` (which overrides launch env). Detection is read-only and server-local: it cannot inspect another launch shell, another machine, or project-local settings. An unknown result is not proof of force support. diff --git a/docs-site/src/content/docs/zh-cn/reference/configuration/server.md b/docs-site/src/content/docs/zh-cn/reference/configuration/server.md index dc96d8ff4b9..9a2d8ceba78 100644 --- a/docs-site/src/content/docs/zh-cn/reference/configuration/server.md +++ b/docs-site/src/content/docs/zh-cn/reference/configuration/server.md @@ -201,3 +201,19 @@ Anthropic OAuth 侧车会复用 opencodex 现有的 Claude Code OAuth 指纹。 主 Codex 账户行中的 `quotaRefresh` 描述额度查询结果,并不代表剩余额度或模型访问权限。读取缓存或未执行查询时,该字段可能省略。查询使用正在运行的代理服务的环境,而不是当前终端的环境。未设置 `proxy` 时保留现有环境;`"auto"` 在启动时读取 Windows 或 macOS 静态 HTTP/HTTPS 设置;macOS 上若有继承代理则跳过读取。macOS 将有效的 `*.` 转为 `.`:`*.local` 使 `foo.local` 和裸域名 `local` 直连,但不匹配 `xlocal`。精确的 `169.254/16`、`169.254.0.0/16`、`fe80::/10` 网段会跳过并给出诊断,因此链路本地 IP 地址使用代理。IP 地址和 `*` 仍可用;其他 CIDR、通配形式和简单主机名例外会在修改环境前拒绝自动发现。不自动处理 PAC/WPAD、仅 SOCKS 的设置或运行中的更改。TUN 测试成功并不能单独证明 HTTP 代理路径正常。命令和状态说明见[英文网络诊断章节](/reference/configuration/server/#codex-quota-network-diagnostics)。 `dropCodexSafetyBuffering`: 不会改变供应商安全策略或拒绝响应。原生 WebSocket `codex.response.metadata.headers` 和 `/responses/compact` 不在过滤范围内。 + +### Forced Claude Code subagent model + +The Subagents page offers **Force all subagents onto one model**, off by default. Select an exposed roster-style id, such as `combo/tev-auto`, then enable the switch. The roster is offered first; unavailable saved roster entries cannot be force targets. + +`ocx agent subagents force combo/tev-auto` sets `claudeCode.subagentModelForce`; `ocx agent subagents force -` clears it. `ocx agent status` reports the setting. `GET /api/subagent-models` returns `force`, `forceAvailable`, and `forceStatus`; `PUT` accepts `{ "force": "combo/tev-auto" }` or `{ "force": null }` without changing the roster. Omitting `force` leaves it unchanged. Invalid or unexposed targets are rejected on write; stale targets are reported and skipped at launch. + +This takes effect on the **next routed `ocx claude` launch**, injecting `CLAUDE_CODE_SUBAGENT_MODEL` as an explicit proxy alias (with `[1m]` only for an authoritative million-token window; native Claude targets use a reversible native alias) and `CLAUDE_CODE_SUBAGENT_MODEL_FORCE=1`. Each nonempty shell-exported variable independently wins. Native launches inject neither variable; plain `claude` is not affected. No plugin files or `settings.json` are modified by this setting. + +Claude Code **2.1.257 or newer** is required for FORCE. Plugin and built-in agents (including Explore/Plan) and per-call model arguments are overridden. Forks and subagent skills with `model: inherit` keep the main conversation model. The main loop and Haiku/small-fast sidecars are unaffected. Existing roster files remain available. + +The dashboard warns about old or unknown CLI versions, unavailable targets, and either variable already present in `settings.json` → `env` (which overrides launch env). Detection is read-only and server-local: it cannot inspect another launch shell, another machine, or project-local settings. An unknown result is not proof of force support. + +## 令牌预留与额度限制 + +如果请求受 `spend.root.maxTokens`、`spend.identity.maxTokens` 或 `spend.pool.maxTokens` 限制,无法记录令牌预留时会拒绝发送,包括跟踪容量已满的情况。没有适用额度限制的请求仍保持仅观察模式。 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 a9809b951f2..f02cf6e6d5d 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 @@ -358,3 +358,15 @@ OpenAI 也遵循此规则:开关不会选择特殊的 922k 模式。有效上 DTO 包含 `autoSwitchThresholdOverride`(整数/null)、`autoSwitchThreshold`(池默认值)、`effectiveAutoSwitchThreshold`。0 只禁用按用量切换;暂停和 429 恢复不变。 HTTP: 400 invalid/unsupported; 404 missing account; `oauth_mutation_busy` on lock contention. + +### Forced Claude Code subagent model + +The Subagents page offers **Force all subagents onto one model**, off by default. Select an exposed roster-style id, such as `combo/tev-auto`, then enable the switch. The roster is offered first; unavailable saved roster entries cannot be force targets. + +`ocx agent subagents force combo/tev-auto` sets `claudeCode.subagentModelForce`; `ocx agent subagents force -` clears it. `ocx agent status` reports the setting. `GET /api/subagent-models` returns `force`, `forceAvailable`, and `forceStatus`; `PUT` accepts `{ "force": "combo/tev-auto" }` or `{ "force": null }` without changing the roster. Omitting `force` leaves it unchanged. Invalid or unexposed targets are rejected on write; stale targets are reported and skipped at launch. + +This takes effect on the **next routed `ocx claude` launch**, injecting `CLAUDE_CODE_SUBAGENT_MODEL` as an explicit proxy alias (with `[1m]` only for an authoritative million-token window; native Claude targets use a reversible native alias) and `CLAUDE_CODE_SUBAGENT_MODEL_FORCE=1`. Each nonempty shell-exported variable independently wins. Native launches inject neither variable; plain `claude` is not affected. No plugin files or `settings.json` are modified by this setting. + +Claude Code **2.1.257 or newer** is required for FORCE. Plugin and built-in agents (including Explore/Plan) and per-call model arguments are overridden. Forks and subagent skills with `model: inherit` keep the main conversation model. The main loop and Haiku/small-fast sidecars are unaffected. Existing roster files remain available. + +The dashboard warns about old or unknown CLI versions, unavailable targets, and either variable already present in `settings.json` → `env` (which overrides launch env). Detection is read-only and server-local: it cannot inspect another launch shell, another machine, or project-local settings. An unknown result is not proof of force support. diff --git a/docs-site/src/content/docs/zh-tw/guides/claude-code.md b/docs-site/src/content/docs/zh-tw/guides/claude-code.md index bc755d3d23e..9233107d1e8 100644 --- a/docs-site/src/content/docs/zh-tw/guides/claude-code.md +++ b/docs-site/src/content/docs/zh-tw/guides/claude-code.md @@ -647,3 +647,17 @@ Claude 模型時自動載入。對於原生透傳,這是正常現象;對於 ### `anthropicAccountPool.routes` 帳戶池啟用時,`anthropicAccountPool.routes` 依模型第一個符合的規則,將首次選擇和 429 重試限制在已儲存帳戶內。沒有可用帳戶時會在本機拒絕;`fallback: true` 才允許使用一般帳戶池。規則不代表帳戶確實有模型權限。 + +### Forced Claude Code subagent model + +The Subagents page offers **Force all subagents onto one model**, off by default. Select an exposed roster-style id, such as `combo/tev-auto`, then enable the switch. The roster is offered first; unavailable saved roster entries cannot be force targets. + +`ocx agent subagents force combo/tev-auto` sets `claudeCode.subagentModelForce`; `ocx agent subagents force -` clears it. `ocx agent status` reports the setting. `GET /api/subagent-models` returns `force`, `forceAvailable`, and `forceStatus`; `PUT` accepts `{ "force": "combo/tev-auto" }` or `{ "force": null }` without changing the roster. Omitting `force` leaves it unchanged. Invalid or unexposed targets are rejected on write; stale targets are reported and skipped at launch. + +This takes effect on the **next routed `ocx claude` launch**, injecting `CLAUDE_CODE_SUBAGENT_MODEL` as an explicit proxy alias (with `[1m]` only for an authoritative million-token window; native Claude targets use a reversible native alias) and `CLAUDE_CODE_SUBAGENT_MODEL_FORCE=1`. Each nonempty shell-exported variable independently wins. Native launches inject neither variable; plain `claude` is not affected. No plugin files or `settings.json` are modified by this setting. + +Claude Code **2.1.257 or newer** is required for FORCE. Plugin and built-in agents (including Explore/Plan) and per-call model arguments are overridden. Forks and subagent skills with `model: inherit` keep the main conversation model. The main loop and Haiku/small-fast sidecars are unaffected. Existing roster files remain available. + +The dashboard warns about old or unknown CLI versions, unavailable targets, and either variable already present in `settings.json` → `env` (which overrides launch env). Detection is read-only and server-local: it cannot inspect another launch shell, another machine, or project-local settings. An unknown result is not proof of force support. + +Explicit gateway selectors on a generated agent request take precedence over its legacy `ocx-route` fallback, even if the saved force setting changes after launch. For shell or settings overrides of generated roster agents, use an explicit gateway alias; bare Claude ids retain the older-client fallback behavior. Native aliases restore their bare model before the existing credential and model-map checks. Connected launches validate force targets against a fresh authenticated gateway catalog; failed discovery skips automatic force injection, and cached context windows alone never prove availability. diff --git a/docs-site/src/content/docs/zh-tw/guides/integrations.md b/docs-site/src/content/docs/zh-tw/guides/integrations.md index 20a31542115..8227cd2b9d5 100644 --- a/docs-site/src/content/docs/zh-tw/guides/integrations.md +++ b/docs-site/src/content/docs/zh-tw/guides/integrations.md @@ -119,7 +119,7 @@ TOML 日期與時間值也會阻止自動重寫:合併步驟會將這些帶有 **Kimi Code 無法持有環境變數參考,** 所以它的設定攜帶的是 `opencodex-loopback` 佔位符而非金鑰。絕不會有任何真實憑證被寫入任何客戶端設定。 -**對 `ocx opencode` 而言,launcher 的 provider 區塊勝出。** 那個 launcher 透過 `OPENCODE_CONFIG_CONTENT` 注入 `provider.opencodex`,比磁碟上相同的條目優先——你其餘的 opencode 設定仍照常套用。當你直接啟動 `opencode` 時,這裡的開關才是關鍵。 +**對 `ocx opencode` 而言,launcher 的 provider 區塊勝出。** 那個 launcher 透過 `OPENCODE_CONFIG_CONTENT` 注入 `provider.opencodex`,比磁碟上相同的條目優先——你其餘的 opencode 設定仍照常套用。launcher 寫入的區塊與匯出使用相同的生效規範模型中繼資料(已知能力與推論選項),未知值不會被捏造;僅輸出上限在未知時使用 schema 必需的 32000 後備值(按 context 截斷)。當你直接啟動 `opencode` 時,這裡的開關才是關鍵。 ## 從終端機 @@ -150,10 +150,10 @@ ocx mcode ``` 完成一次連接後,`ocx sync` 與 `POST /api/sync` 會更新 OpenCodex 已擁有的 -MCode、Pi、Aside、Raycast 與 omo 目錄。proxy 啟動也會更新已擁有的 Raycast 目錄。 -模型可見性、provider 或 preset 變更會更新 Pi、Aside、Raycast 與 omo。若區塊已刪除、 +MCode、Pi、Aside、Raycast、omo、OpenCode 與 Kilo 目錄。proxy 啟動也會更新已擁有的 Raycast 目錄。 +模型可見性、provider 或 preset 變更會更新 Pi、Aside、Raycast、omo、OpenCode 與 Kilo。若區塊已刪除、 遭外部修改、不安全或由你手動移除,sync 會保持原檔不動;只有在你確定要重新 -連接時才再次執行 enable。 +連接時才再次執行 enable。啟動新的 Pi、OpenCode 或 Kilo 工作階段,才能載入更新後的檔案。 另一個 MiniMax 平台 CLI(`mmx`)不是檔案開關整合。其文字命令使用 MiniMax 的 Anthropic 相容端點,因此 OpenCodex 提供憑證隔離、僅限 loopback 的 launcher: @@ -206,7 +206,7 @@ ocx integration client restore --op ## Kilo -Kilo 只會把 `provider.opencodex` 寫入 `~/.config/kilo` 下最先存在的全域檔(`XDG_CONFIG_HOME` 會移動該目錄;若沒有任何候選檔則建立 `kilo.jsonc`)。若另一個候選檔也定義 `provider.opencodex`,狀態會回報衝突且套用會拒絕。其他鍵保持不變。套用會重寫整個檔案,因此不會保留註解與尾隨逗號。請在 Kilo 中選擇 `opencodex/<模型>`。 +Kilo 只會把 `provider.opencodex` 寫入 `~/.config/kilo` 下最先存在的全域檔(`XDG_CONFIG_HOME` 會移動該目錄;若沒有任何候選檔則建立 `kilo.jsonc`)。若另一個候選檔也定義 `provider.opencodex`,狀態會回報衝突且套用會拒絕。其他鍵保持不變。套用會重寫整個檔案,因此不會保留註解與尾隨逗號。每個模型的推論選擇會以變體映射的形式寫入 provider 區塊,與 OpenCode 匯出使用相同的生效規範中繼資料。`ocx sync` 與 `POST /api/sync` 會重新整理 OpenCodex 擁有的 Kilo 區塊,新的 Kilo 工作階段會載入更新後的檔案。請在 Kilo 中選擇 `opencodex/<模型>`。 即使其他候選檔發生衝突或無法剖析,停用仍可移除已記錄檔案中由 OpenCodex 管理的區塊;其他候選檔不會變動。 diff --git a/docs-site/src/content/docs/zh-tw/guides/remote-link.md b/docs-site/src/content/docs/zh-tw/guides/remote-link.md index f30a8987c43..87233145b80 100644 --- a/docs-site/src/content/docs/zh-tw/guides/remote-link.md +++ b/docs-site/src/content/docs/zh-tw/guides/remote-link.md @@ -11,7 +11,7 @@ description: 透過 SSH 連接 OpenCodex Home 電腦與 Child 電腦。 - 對於由 Child 發起的連結,Child 必須能使用 OpenSSH 金鑰登入 Home(不支援密碼登入)。 - Child 已安裝 OpenCodex 2.66.0 或更新版本(由 Child 發起的連結也要求 Home 符合)。 - 兩台電腦執行 macOS 或 Linux。 -- 發起連結的儀表板需在那台電腦本機開啟(獨立安裝的瀏覽器或桌面應用程式),或透過已配對的 Hub 工作階段開啟。 +- 從 Home 新增 Child 的儀表板需在 Home 上開啟,或使用已配對的 Hub 工作階段。將目前電腦轉換為 Child 需要由操作者配對的儀表板工作階段;未經憑證簽發的本機工作階段不能提交這項路由變更。 密碼 SSH 和 Windows 不在目前流程中。連結可以從任一端發起:依下文從 Home 發起,或依「將這台電腦連線為 Child」一節從 Child 發起。 @@ -25,6 +25,17 @@ description: 透過 SSH 連接 OpenCodex Home 電腦與 Child 電腦。 儀表板不會要求輸入權杖。它會先探測主機,只有明確確認指紋後才能套用連結。 +新增 Child 視窗會說明 Child 透過 SSH 使用此 Home 的供應商,並列出上述必要條件。別名來自執行 OpenCodex 的 Home 上的 `~/.ssh/config`,不一定是顯示瀏覽器的電腦。如果探索成功但沒有主機,請新增如下 `Host` 項目後重新掃描。也可以手動輸入現有別名;選取或輸入別名後即可啟用連線測試。視窗還提供本指南的連結。 + +```sshconfig +Host devbox + HostName devbox.example.com + User you + IdentityFile ~/.ssh/id_ed25519 +``` + +如果探索失敗,視窗會顯示載入失敗、可用的請求原因和重試按鈕,而不是聲稱清單為空。連線測試失敗的具體原因與清理後的 SSH 提示只在作用中視窗顯示,不會在背後重複顯示。檢查原因,必要時使用下方 SSH 診斷方法,然後重試。重新掃描會保留輸入的別名,但連線前必須重新檢查指紋。 + ## 將這台電腦連線為 Child 在要使用 Home 供應商的電腦上: @@ -36,7 +47,9 @@ description: 透過 SSH 連接 OpenCodex Home 電腦與 Child 電腦。 連線會重新啟動這台電腦上的 OpenCodex。已在執行的 Codex 請求會先完成,重新啟動期間新的請求可能在最多一分鐘內失敗。之後儀表板會自動重新載入並顯示 Child 連結。Codex 會繼續使用這台電腦上的 `http://127.0.0.1:/v1`,不需要設定權杖或環境變數:本機 OpenCodex 會把每個請求轉送給 Home,由 Home 以它自己的供應商與帳戶提供服務。 -只有當 OpenCodex 在其設定的連接埠上執行時,才能選擇 **Child** 角色,因為 Child 會在正好這個連接埠上重新啟動。如果儀表板提示 OpenCodex 未在其設定的連接埠上執行,請先在該連接埠上重新啟動它。 +如果 **Child** 提示先配對這台電腦,請在這台電腦上開啟設定的 HTTP 回環 IP 位址儀表板,例如 `http://127.0.0.1:`。只有在缺少配對且儀表板與 API 使用相同的回環來源時,才會顯示本機配對表單。複製表單中的 `ocx gui pair --origin "http://127.0.0.1:"` 命令,在這台電腦的終端機中執行,再將一次性代碼貼到表單中。必須使用表單顯示的確切來源;供應商 API 金鑰或管理員權杖不是配對代碼。缺少配對與設定的連接埠不符是不同的原因。 + +選擇 **Child** 角色還要求 OpenCodex 以獨立執行模式在其設定的連接埠上執行,因為 Child 會在正好這個連接埠上重新啟動。如果儀表板提示 OpenCodex 未在其設定的連接埠上執行,請先在該連接埠上重新啟動它。 ## 連結狀態 diff --git a/docs-site/src/content/docs/zh-tw/reference/cli/agents.md b/docs-site/src/content/docs/zh-tw/reference/cli/agents.md index f8d3b04c87a..eb47868e3dc 100644 --- a/docs-site/src/content/docs/zh-tw/reference/cli/agents.md +++ b/docs-site/src/content/docs/zh-tw/reference/cli/agents.md @@ -235,3 +235,15 @@ ocx system codex-cli-update attest --candidate --npm-prefix ...` 檢查並安全地修改已驗證的 OpenCodex 設定。`show` 與 `get` 會遮罩秘密。匯入在寫入前驗證且需要 `--yes`。 + +### Forced Claude Code subagent model + +The Subagents page offers **Force all subagents onto one model**, off by default. Select an exposed roster-style id, such as `combo/tev-auto`, then enable the switch. The roster is offered first; unavailable saved roster entries cannot be force targets. + +`ocx agent subagents force combo/tev-auto` sets `claudeCode.subagentModelForce`; `ocx agent subagents force -` clears it. `ocx agent status` reports the setting. `GET /api/subagent-models` returns `force`, `forceAvailable`, and `forceStatus`; `PUT` accepts `{ "force": "combo/tev-auto" }` or `{ "force": null }` without changing the roster. Omitting `force` leaves it unchanged. Invalid or unexposed targets are rejected on write; stale targets are reported and skipped at launch. + +This takes effect on the **next routed `ocx claude` launch**, injecting `CLAUDE_CODE_SUBAGENT_MODEL` as an explicit proxy alias (with `[1m]` only for an authoritative million-token window; native Claude targets use a reversible native alias) and `CLAUDE_CODE_SUBAGENT_MODEL_FORCE=1`. Each nonempty shell-exported variable independently wins. Native launches inject neither variable; plain `claude` is not affected. No plugin files or `settings.json` are modified by this setting. + +Claude Code **2.1.257 or newer** is required for FORCE. Plugin and built-in agents (including Explore/Plan) and per-call model arguments are overridden. Forks and subagent skills with `model: inherit` keep the main conversation model. The main loop and Haiku/small-fast sidecars are unaffected. Existing roster files remain available. + +The dashboard warns about old or unknown CLI versions, unavailable targets, and either variable already present in `settings.json` → `env` (which overrides launch env). Detection is read-only and server-local: it cannot inspect another launch shell, another machine, or project-local settings. An unknown result is not proof of force support. diff --git a/docs-site/src/content/docs/zh-tw/reference/configuration/server.md b/docs-site/src/content/docs/zh-tw/reference/configuration/server.md index dd8a0e472dc..00fd7020f53 100644 --- a/docs-site/src/content/docs/zh-tw/reference/configuration/server.md +++ b/docs-site/src/content/docs/zh-tw/reference/configuration/server.md @@ -218,3 +218,15 @@ Anthropic OAuth sidecar 重用 opencodex 既有的 Claude Code OAuth 指紋。 ## Codex 配額網路診斷 主 Codex 帳戶列中的 `quotaRefresh` 描述配額查詢結果,並不代表剩餘配額或模型存取權限。讀取快取或未執行查詢時,這個欄位可能省略。查詢使用執行中代理服務的環境,而不是目前終端機的環境。未設定 `proxy` 時保留既有環境;`"auto"` 在啟動時讀取 Windows 或 macOS 靜態 HTTP/HTTPS 設定;macOS 上若有繼承代理則略過讀取。macOS 會將有效的 `*.` 轉成 `.`:`*.local` 讓 `foo.local` 與裸網域 `local` 直連,但不比對 `xlocal`。精確的 `169.254/16`、`169.254.0.0/16`、`fe80::/10` 網段會略過並顯示診斷,因此鏈路本機 IP 位址使用代理。IP 位址與 `*` 仍可使用;其他 CIDR、萬用字元形式及簡單主機名稱例外會在修改環境前拒絕自動探索。不會自動處理 PAC/WPAD、僅 SOCKS 的設定或執行中的變更。TUN 測試成功本身不能證明 HTTP 代理路徑正常。命令與狀態說明請見[英文網路診斷章節](/reference/configuration/server/#codex-quota-network-diagnostics)。 + +### Forced Claude Code subagent model + +The Subagents page offers **Force all subagents onto one model**, off by default. Select an exposed roster-style id, such as `combo/tev-auto`, then enable the switch. The roster is offered first; unavailable saved roster entries cannot be force targets. + +`ocx agent subagents force combo/tev-auto` sets `claudeCode.subagentModelForce`; `ocx agent subagents force -` clears it. `ocx agent status` reports the setting. `GET /api/subagent-models` returns `force`, `forceAvailable`, and `forceStatus`; `PUT` accepts `{ "force": "combo/tev-auto" }` or `{ "force": null }` without changing the roster. Omitting `force` leaves it unchanged. Invalid or unexposed targets are rejected on write; stale targets are reported and skipped at launch. + +This takes effect on the **next routed `ocx claude` launch**, injecting `CLAUDE_CODE_SUBAGENT_MODEL` as an explicit proxy alias (with `[1m]` only for an authoritative million-token window; native Claude targets use a reversible native alias) and `CLAUDE_CODE_SUBAGENT_MODEL_FORCE=1`. Each nonempty shell-exported variable independently wins. Native launches inject neither variable; plain `claude` is not affected. No plugin files or `settings.json` are modified by this setting. + +Claude Code **2.1.257 or newer** is required for FORCE. Plugin and built-in agents (including Explore/Plan) and per-call model arguments are overridden. Forks and subagent skills with `model: inherit` keep the main conversation model. The main loop and Haiku/small-fast sidecars are unaffected. Existing roster files remain available. + +The dashboard warns about old or unknown CLI versions, unavailable targets, and either variable already present in `settings.json` → `env` (which overrides launch env). Detection is read-only and server-local: it cannot inspect another launch shell, another machine, or project-local settings. An unknown result is not proof of force support. 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 2ef37691a42..02aa615fa60 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 @@ -337,3 +337,15 @@ OpenAI 也遵循此規則:開關不會選擇特殊的 922k 模式。生效中 DTO 包含 `autoSwitchThresholdOverride`(整數/null)、`autoSwitchThreshold`(集區預設值)、`effectiveAutoSwitchThreshold`。0 只停用依用量切換;暫停和 429 復原不變。 HTTP: 400 invalid/unsupported; 404 missing account; `oauth_mutation_busy` on lock contention. + +### Forced Claude Code subagent model + +The Subagents page offers **Force all subagents onto one model**, off by default. Select an exposed roster-style id, such as `combo/tev-auto`, then enable the switch. The roster is offered first; unavailable saved roster entries cannot be force targets. + +`ocx agent subagents force combo/tev-auto` sets `claudeCode.subagentModelForce`; `ocx agent subagents force -` clears it. `ocx agent status` reports the setting. `GET /api/subagent-models` returns `force`, `forceAvailable`, and `forceStatus`; `PUT` accepts `{ "force": "combo/tev-auto" }` or `{ "force": null }` without changing the roster. Omitting `force` leaves it unchanged. Invalid or unexposed targets are rejected on write; stale targets are reported and skipped at launch. + +This takes effect on the **next routed `ocx claude` launch**, injecting `CLAUDE_CODE_SUBAGENT_MODEL` as an explicit proxy alias (with `[1m]` only for an authoritative million-token window; native Claude targets use a reversible native alias) and `CLAUDE_CODE_SUBAGENT_MODEL_FORCE=1`. Each nonempty shell-exported variable independently wins. Native launches inject neither variable; plain `claude` is not affected. No plugin files or `settings.json` are modified by this setting. + +Claude Code **2.1.257 or newer** is required for FORCE. Plugin and built-in agents (including Explore/Plan) and per-call model arguments are overridden. Forks and subagent skills with `model: inherit` keep the main conversation model. The main loop and Haiku/small-fast sidecars are unaffected. Existing roster files remain available. + +The dashboard warns about old or unknown CLI versions, unavailable targets, and either variable already present in `settings.json` → `env` (which overrides launch env). Detection is read-only and server-local: it cannot inspect another launch shell, another machine, or project-local settings. An unknown result is not proof of force support. diff --git a/gui/design-system/components.md b/gui/design-system/components.md index 309bbaf5948..bbc15fe18d9 100644 --- a/gui/design-system/components.md +++ b/gui/design-system/components.md @@ -8,6 +8,7 @@ - `.nav-item`은 아이콘 17px, control text, 4px 세로 간격을 사용한다. - hover와 active는 같은 surface family를 쓰되 active는 semibold로 구분한다. - 메뉴마다 margin을 직접 추가하지 않고 `.sidebar nav`의 `gap`을 사용한다. +- 데스크톱 레이아웃에서 `.sidebar nav`는 스크롤 영역이고 footer(언어, 테마, 줌, 프록시)는 항상 보인다. 창이 낮거나 페이지 줌으로 CSS 뷰포트가 줄어도 footer 컨트롤에 닿을 수 있어야 한다. 데스크톱 셸의 줌 컨트롤(`.zoom-control`)은 테마 행과 같은 행 높이를 쓴다. ## Buttons diff --git a/gui/index.html b/gui/index.html index 33b3b525656..10cc833c9f8 100644 --- a/gui/index.html +++ b/gui/index.html @@ -17,6 +17,7 @@ // FOUT guard: set before first paint so :lang() font stacks apply immediately. // Mirrors detectInitial() + LOCALES in src/i18n/shared.ts. var langMap = { en: "en", de: "de", fr: "fr", ko: "ko", zh: "zh-CN", "zh-TW": "zh-TW", ru: "ru", ja: "ja", tr: "tr", vi: "vi" }; + langMap.pt = "pt-BR"; var stored = null; try { stored = localStorage.getItem("ocx-lang"); @@ -29,7 +30,7 @@ if (nav.indexOf("zh") === 0) { locale = /tw|hk|mo|hant/.test(nav) ? "zh-TW" : "zh"; } else { - locale = ["de", "fr", "ko", "ru", "ja", "tr", "vi"].filter(function (c) { return nav.indexOf(c) === 0; })[0] || "en"; + locale = ["de", "fr", "ko", "ru", "ja", "tr", "vi", "pt"].filter(function (c) { return nav.indexOf(c) === 0; })[0] || "en"; } } document.documentElement.lang = langMap[locale]; diff --git a/gui/package.json b/gui/package.json index d7000ade3d2..33b37040cfb 100644 --- a/gui/package.json +++ b/gui/package.json @@ -13,7 +13,8 @@ "doctor:full": "npx --yes react-doctor@0.9.11 --verbose --scope full --no-telemetry", "preview": "vite preview", "test:sidebar-version": "bun tests/sidebar-version-browser.ts", - "test:quota-hover": "bun tests/quota-summary-hover-browser.ts" + "test:quota-hover": "bun tests/quota-summary-hover-browser.ts", + "test:shared-scroll": "bun tests/shared-scroll-browser.ts" }, "dependencies": { "@tanstack/react-virtual": "^3.14.9", diff --git a/gui/src/App.tsx b/gui/src/App.tsx index a12548a13a9..f6b49bfffde 100644 --- a/gui/src/App.tsx +++ b/gui/src/App.tsx @@ -30,7 +30,10 @@ import { requestProxyStop } from "./stop-proxy"; import { useCodexRestart } from "./use-codex-restart"; import { confirmAction } from "./action-dialogs"; import { hostOs, isDesktopShell, isExternalLink, openDesktopUpdatePage } from "./lib/desktop-shell"; +import { zoomManagedOn } from "./lib/desktop-zoom"; import { useSidebarCollapse } from "./use-sidebar-collapse"; +import { useDesktopZoom } from "./use-desktop-zoom"; +import { DesktopZoomControl } from "./components/desktop-zoom-control"; import { MainTopStrip, SidebarTopStrip } from "./components/app-titlebar"; import { watchMacTitlebarMetrics, windowChromeHandlers } from "./lib/window-chrome"; @@ -212,6 +215,8 @@ export default function App() { const desktopShell = isDesktopShell(); const { collapsed: navCollapsed, toggle: toggleNavCollapse } = useSidebarCollapse({ shortcut: desktopShell }); const desktopMac = desktopShell && hostOs() === "macos"; + const zoomManaged = desktopShell && zoomManagedOn(hostOs()); + const desktopZoom = useDesktopZoom({ managed: zoomManaged }); const appRef = useRef(null); useEffect(() => { if (desktopMac && appRef.current) return watchMacTitlebarMetrics(appRef.current); @@ -484,6 +489,10 @@ export default function App() { aria-label={`${t("theme.label")}: ${t(THEME_TKEY[theme])}`} title={`${t("theme.label")}: ${t(THEME_TKEY[theme])}`}> {t(THEME_TKEY[theme])} + {zoomManaged && ( + + )}
{t("dash.actions")}
diff --git a/gui/src/api.ts b/gui/src/api.ts index 420c04d3f39..48d47eac70c 100644 --- a/gui/src/api.ts +++ b/gui/src/api.ts @@ -320,7 +320,11 @@ export function installApiAuthFetch(): void { if (!classified) return originalFetch(input, init); const state = runtime(classified.plane); const token = state.session.token; - const [firstInput, firstInit] = withAuth(classified.plane, input, init); + const method = (init?.method ?? (input instanceof Request ? input.method : "GET")).toUpperCase(); + // Pairing exchanges must not carry the old shared-plane credential. The relay's + // separate machine-session headers are still attached by sessionHeaders(). + const pairingExchange = classified.bootstrap && method === "POST"; + const [firstInput, firstInit] = withAuth(classified.plane, input, init, pairingExchange ? null : undefined); const response = await originalFetch(firstInput, firstInit); if (classified.bootstrap || response.status !== 401) return response; const refreshed = state.session.token; diff --git a/gui/src/components/CompactionRoutingPanel.tsx b/gui/src/components/CompactionRoutingPanel.tsx index 6875b7ab3fa..912a7a028a4 100644 --- a/gui/src/components/CompactionRoutingPanel.tsx +++ b/gui/src/components/CompactionRoutingPanel.tsx @@ -7,9 +7,10 @@ import { requireJson, type ModelInfo } from "../pages/dashboard-shared"; import { comboModelId, parseComboList } from "../combo-workspace-data"; import { formatNamespacedModelId } from "../provider-icons"; -type Setting = { model: string; reasoningEffort?: string; triggers?: string[] } | null; +type Setting = { model: string; reasoningEffort?: string; triggers?: string[]; sourceModels?: string[] } | null; const EFFORTS = ["none", "minimal", "low", "medium", "high", "xhigh", "max", "ultra"]; const TRIGGERS = ["manual", "auto"]; +const SOURCE_RENDER_CAP = 300; /** * One picker over the two trigger sets the config allows. "manual" is sent as an omitted * `triggers`, so the default save keeps the exact payload the manual-only override used. @@ -20,6 +21,16 @@ const TRIGGER_LABELS: Record = { "manual+auto": "compactionRouting.triggersBoth", "auto": "compactionRouting.triggersAuto", }; +/** + * "all" is sent as an omitted `sourceModels`, so the default save keeps the exact payload the + * unscoped override used. `selected` requires at least one checked selector: the config schema + * rejects an empty list and would silently drop the whole override. + */ +const SCOPE_CHOICES = ["all", "selected"] as const; +const SCOPE_LABELS: Record = { + "all": "compactionRouting.sourcesAll", + "selected": "compactionRouting.sourcesSelected", +}; function triggersToChoice(value: string[] | undefined): string { if (!value) return "manual"; @@ -62,6 +73,9 @@ function readSetting(payload: { compactionRouting?: unknown }): Setting { const effort = "reasoningEffort" in value ? value.reasoningEffort : undefined; if (effort !== undefined && (typeof effort !== "string" || !EFFORTS.includes(effort))) throw new Error("invalid effort"); const triggers = "triggers" in value ? value.triggers : undefined; + const sourceModels = "sourceModels" in value ? value.sourceModels : undefined; + if (sourceModels !== undefined && (!Array.isArray(sourceModels) || !sourceModels.length + || !sourceModels.every(entry => typeof entry === "string" && entry.trim()))) throw new Error("invalid sourceModels"); if (triggers !== undefined && (!Array.isArray(triggers) || triggers.length === 0 || !triggers.every(entry => typeof entry === "string" && TRIGGERS.includes(entry)) || new Set(triggers).size !== triggers.length)) throw new Error("invalid triggers"); @@ -69,19 +83,38 @@ function readSetting(payload: { compactionRouting?: unknown }): Setting { model: value.model, ...(effort ? { reasoningEffort: effort as string } : {}), ...(triggers ? { triggers: triggers as string[] } : {}), + ...(sourceModels ? { sourceModels: sourceModels as string[] } : {}), }; } -export default function CompactionRoutingPanel(props: { apiBase: string; models: ModelInfo[] }) { +/** A provider name says who is configured; the endpoint host says where bytes actually go. */ +function hostOf(baseUrl: string): string { + if (!baseUrl) return ""; + try { + return new URL(baseUrl).hostname || baseUrl; + } catch { + return baseUrl; + } +} + +function endpointLabel(name: string, hosts: ReadonlyMap): string { + const host = hosts.get(name); + return host ? `${name} (${host})` : name; +} + +export default function CompactionRoutingPanel(props: { apiBase: string; models: ModelInfo[]; providers?: Array<{ name: string; baseUrl: string }> }) { return ; } -function CompactionRoutingControls({ apiBase, models }: { apiBase: string; models: ModelInfo[] }) { +function CompactionRoutingControls({ apiBase, models, providers: providerList = [] }: { apiBase: string; models: ModelInfo[]; providers?: Array<{ name: string; baseUrl: string }> }) { const t = useT(); const [saved, setSaved] = useState(undefined); const [model, setModel] = useState(""); const [effort, setEffort] = useState(""); const [triggers, setTriggers] = useState("manual"); + const [scope, setScope] = useState("all"); + const [sources, setSources] = useState([]); + const [sourceQuery, setSourceQuery] = useState(""); const [busy, setBusy] = useState(false); const [loadError, setLoadError] = useState(false); const [feedback, setFeedback] = useState<"saved" | "failed" | null>(null); @@ -94,8 +127,17 @@ function CompactionRoutingControls({ apiBase, models }: { apiBase: string; model setModel(value?.model ?? ""); setEffort(value?.reasoningEffort ?? ""); setTriggers(triggersToChoice(value?.triggers)); + setScope(value?.sourceModels ? "selected" : "all"); + setSources(value?.sourceModels ?? []); }, []); + const toggleSource = (selector: string) => { + setSources(current => current.includes(selector) + ? current.filter(entry => entry !== selector) + : [...current, selector]); + setFeedback(null); + }; + const load = useCallback(async () => { if (pending.current) return; const request = createBoundedFetch(15_000); @@ -144,6 +186,7 @@ function CompactionRoutingControls({ apiBase, models }: { apiBase: string; model model, ...(effort ? { reasoningEffort: effort } : {}), ...(choiceToTriggers(triggers) ? { triggers: choiceToTriggers(triggers) } : {}), + ...(scope === "selected" && sources.length > 0 ? { sourceModels: sources } : {}), } : null, }), @@ -166,10 +209,37 @@ function CompactionRoutingControls({ apiBase, models }: { apiBase: string; model const options = [{ value: "", label: t("compactionRouting.currentModel") }, ...[...new Set([...models.map(item => item.namespaced), ...(model ? [model] : [])])] .map(value => ({ value, label: formatNamespacedModelId(value, t) }))]; + // Configured providers count even without a catalog row: they can still serve + // provider-qualified ids, so their wildcard must be selectable here. + const providerWildcards = [...new Set([ + ...providerList.map(provider => provider.name), + ...models.flatMap(item => item.provider ? [item.provider] : []), + ])].map(provider => `${provider}/*`); + const knownSelectors = new Set([...providerWildcards, ...models.map(item => item.namespaced)]); + // Saved selectors the catalog no longer lists stay visible, so saving never drops them by + // omission and the operator removes them deliberately. + const savedOnlySelectors = (saved?.sourceModels ?? []).filter(selector => !knownSelectors.has(selector)); const disabled = busy || saved === undefined || loadError; + const savedScope = saved?.sourceModels ? "selected" : "all"; + const savedSources = new Set(saved?.sourceModels); const dirty = model !== (saved?.model ?? "") || effort !== (saved?.reasoningEffort ?? "") - || triggers !== triggersToChoice(saved?.triggers); + || triggers !== triggersToChoice(saved?.triggers) + || scope !== savedScope + || (scope === "selected" + && (sources.length !== (saved?.sourceModels?.length ?? 0) + || sources.some(selector => !savedSources.has(selector)))); + const scopeEmpty = scope === "selected" && sources.length === 0; + const selectedSources = new Set(sources); + const filteredSources = [...new Set(models.map(item => item.namespaced))] + .filter(selector => selector.toLowerCase().includes(sourceQuery.trim().toLowerCase())); + const sourceRow = (selector: string) => ( + + ); // Ask what the selection resolves to instead of reading its name. An aliased combo answers // here exactly like a prefixed one (#5216). const comboTargets = comboProviders.get(model); @@ -178,9 +248,21 @@ function CompactionRoutingControls({ apiBase, models }: { apiBase: string; model // an ordinary provider named "combo" — worse than the alias gap this fixes. const isCombo = comboTargets !== undefined || model.startsWith(comboModelId("")); const namespace = model.slice(0, Math.max(model.indexOf("/"), 0)); - const provider = isCombo ? "" : (namespace || model); - const providers = comboTargets?.join(", ") || t("compactionRouting.comboProvidersUnknown"); + const endpointHosts = new Map(providerList.map(entry => [entry.name, hostOf(entry.baseUrl)] as const).filter(([, host]) => host)); + const provider = isCombo ? "" : endpointLabel(namespace || model, endpointHosts); + const providers = comboTargets?.map(name => endpointLabel(name, endpointHosts)).join(", ") || t("compactionRouting.comboProvidersUnknown"); const routesAutomatic = triggers !== "manual"; + const scopedSources = sources.join(", "); + // A scoped override only covers the checked sources, so the disclosure names them instead of + // claiming every request; an empty selection covers nothing and shows no destination claim. + const warningText = !model || scopeEmpty ? null + : isCombo + ? (scope === "selected" + ? t("compactionRouting.comboWarningScoped", { combo: model, providers, sources: scopedSources }) + : t("compactionRouting.comboWarning", { combo: model, providers })) + : (scope === "selected" + ? t("compactionRouting.providerWarningScoped", { provider, sources: scopedSources }) + : t("compactionRouting.providerWarning", { provider })); return (
@@ -190,27 +272,66 @@ function CompactionRoutingControls({ apiBase, models }: { apiBase: string; model
{t("compactionRouting.description")}
{t("compactionRouting.dataNotice")}
{t("compactionRouting.effortHint")}
+
{t("compactionRouting.sourcesHint")}
-
- ({ value, label: t(TRIGGER_LABELS[value]!) }))} - onChange={value => { setTriggers(value); setFeedback(null); }} /> - { setModel(value); if (!value) { setEffort(""); setTriggers("manual"); setScope("all"); setSources([]); } setFeedback(null); }} /> +
+
+ + ({ value, label: t(SCOPE_LABELS[value]!) }))} + onChange={value => { setScope(value); setFeedback(null); }} /> +
+
+ + setSourceQuery(event.target.value)} /> + {filteredSources.slice(0, SOURCE_RENDER_CAP).map(sourceRow)} + {filteredSources.length > SOURCE_RENDER_CAP &&

+ {t("pws.modelsTruncated", { shown: SOURCE_RENDER_CAP, total: filteredSources.length })} +

} + } + {savedOnlySelectors.length > 0 &&
+ {t("compactionRouting.sourcesSaved")} + {savedOnlySelectors.map(sourceRow)} +
} + {scopeEmpty &&
{t("compactionRouting.sourcesNone")}
} +
+
} + {warningText &&
{warningText}
} {model && routesAutomatic &&
{t("compactionRouting.autoNotice")}
} {loadError &&
{t("compactionRouting.loadFailed")}
} {feedback === "failed" &&
{t("compactionRouting.saveFailed")}
} diff --git a/gui/src/components/QuotaBars.tsx b/gui/src/components/QuotaBars.tsx index ec18b78b65a..fc72b0bb5f3 100644 --- a/gui/src/components/QuotaBars.tsx +++ b/gui/src/components/QuotaBars.tsx @@ -180,6 +180,8 @@ function bcp47(locale: Locale): string { return "tr-TR"; case "vi": return "vi-VN"; + case "pt": + return "pt-BR"; default: { const _exhaustive: never = locale; return _exhaustive; diff --git a/gui/src/components/desktop-zoom-control.tsx b/gui/src/components/desktop-zoom-control.tsx new file mode 100644 index 00000000000..03998109417 --- /dev/null +++ b/gui/src/components/desktop-zoom-control.tsx @@ -0,0 +1,32 @@ +import { IconMinus, IconPlus } from "../icons"; +import { useT } from "../i18n/shared"; +import type { ZoomAction } from "../lib/desktop-zoom"; + +interface DesktopZoomControlProps { + percent: number; + canZoomIn: boolean; + canZoomOut: boolean; + onStep: (action: ZoomAction) => void; +} + +/** Sidebar row for the desktop window's page zoom; the same steps as Ctrl/Cmd + plus, minus and zero. */ +export function DesktopZoomControl({ percent, canZoomIn, canZoomOut, onStep }: DesktopZoomControlProps) { + const t = useT(); + return ( +
+ {t("zoom.label")} + + + +
+ ); +} diff --git a/gui/src/components/provider-workspace/ProviderCapacityQuota.tsx b/gui/src/components/provider-workspace/ProviderCapacityQuota.tsx index dce977c5c63..992e9d6df3c 100644 --- a/gui/src/components/provider-workspace/ProviderCapacityQuota.tsx +++ b/gui/src/components/provider-workspace/ProviderCapacityQuota.tsx @@ -27,6 +27,7 @@ function bcp47(locale: Locale): string { case "ja": return "ja-JP"; case "tr": return "tr-TR"; case "vi": return "vi-VN"; + case "pt": return "pt-BR"; default: { const _exhaustive: never = locale; return _exhaustive; diff --git a/gui/src/components/subagents-workspace/SubagentForceControl.tsx b/gui/src/components/subagents-workspace/SubagentForceControl.tsx new file mode 100644 index 00000000000..57d74a1a75e --- /dev/null +++ b/gui/src/components/subagents-workspace/SubagentForceControl.tsx @@ -0,0 +1,118 @@ +import { useEffect, useRef, useState } from "react"; +import { useT } from "../../i18n/shared"; +import { Notice, Switch } from "../../ui"; +import { readJsonOrThrow } from "../../fetch-json"; + +interface ForceState { + force: string | null; + forceAvailable: string[]; + forceStatus: { + targetValid: boolean; + version: string | null; + support: "supported" | "unsupported" | "unknown"; + settingsOverride: boolean; + settingsReadable: boolean; + } | null; +} + +function forceModelOptions(roster: readonly string[], exposed: readonly string[]): string[] { + const available = new Set(exposed); + return [...new Set([...roster.filter(model => available.has(model)), ...exposed])]; +} + +/** Endpoint-keyed by the page so a late reply cannot update a different server's controls. */ +export default function SubagentForceControl({ apiBase, roster }: { apiBase: string; roster: string[] }) { + const t = useT(); + const [state, setState] = useState(null); + const [selection, setSelection] = useState(""); + const [busy, setBusy] = useState(false); + const [error, setError] = useState(""); + const [saved, setSaved] = useState(false); + const [retry, setRetry] = useState(0); + const [loaded, setLoaded] = useState<{ retry: number; t: typeof t } | null>(null); + const pending = busy || loaded?.retry !== retry || loaded.t !== t; + const inFlight = useRef(false); + const active = useRef(false); + + useEffect(() => { + active.current = true; + inFlight.current = true; + const controller = new AbortController(); + let cancelled = false; + void (async () => { + try { + const response = await fetch(`${apiBase}/api/subagent-models`, { signal: controller.signal }); + const data = await readJsonOrThrow>(response, t("sub.loadFail")); + if (cancelled) return; + const next = { force: data?.force ?? null, forceAvailable: data?.forceAvailable ?? [], forceStatus: data?.forceStatus ?? null }; + setState(next); + setSelection(next.force ?? ""); + setError(""); + } catch { + if (!cancelled) setError(t("sub.loadFail")); + } finally { + if (!cancelled) { inFlight.current = false; setLoaded({ retry, t }); } + } + })(); + return () => { cancelled = true; active.current = false; controller.abort(); }; + }, [apiBase, retry, t]); + + async function save(force: string | null) { + if (!state || pending || inFlight.current) return; + inFlight.current = true; + setBusy(true); + setError(""); + setSaved(false); + try { + const response = await fetch(`${apiBase}/api/subagent-models`, { + method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ force }), + }); + const data = await readJsonOrThrow<{ force: string | null }>(response, t("sub.saveFailed")); + if (!active.current) return; + setState(current => current ? { ...current, force: data?.force ?? null, forceStatus: null } : current); + setSelection(data?.force ?? selection); + setSaved(true); + setRetry(value => value + 1); + } catch { + if (active.current) setError(t("sub.saveFailed")); + } finally { + inFlight.current = false; + if (active.current) setBusy(false); + } + } + + const options = forceModelOptions(roster, state?.forceAvailable ?? []); + const selected = state?.force ?? selection; + const valid = options.includes(selected); + const status = state?.forceStatus; + return ( +
+

{t("sub.forceTitle")}

+ { void save(state?.force ? null : selection); }} /> + +

{t("sub.forceHelp")}

+

{t("sub.forceScope")}

+ {state?.force && (!valid || status?.targetValid === false) && {t("sub.forceInvalid")}} + {state?.force && status?.support === "unsupported" && {t("sub.forceOld")}} + {state?.force && (!status || status.support === "unknown") && {t("sub.forceUnknown")}} + {state?.force && status?.settingsOverride && {t("sub.forceOverride")}} + {state?.force && status?.settingsReadable === false && {t("sub.forceSettingsUnknown")}} + {saved && {t("sub.forceSaved")}} + {error && {error}} +
+ ); +} diff --git a/gui/src/connect-pairing.tsx b/gui/src/connect-pairing.tsx index c6c9750b1ab..e541b6e1cbb 100644 --- a/gui/src/connect-pairing.tsx +++ b/gui/src/connect-pairing.tsx @@ -7,9 +7,12 @@ import { useCopyFeedback } from "./components/use-copy-feedback"; export function ConnectPairingForm({ target, onConnected, + local = false, }: { target: ApiTarget; onConnected: () => void; + /** Explicit local pairing for a standalone link join; never an automatic bootstrap. */ + local?: boolean; }) { const t = useT(); const [grant, setGrant] = useState(""); @@ -40,15 +43,15 @@ export function ConnectPairingForm({ }; return
-

{t("connection.pairing.title")}

-

{t("connection.pairing.hub")}: {target.serverOrigin}

-

{t("connection.pairing.getCode")}

+

{t(local ? "connection.pairing.code" : "connection.pairing.title")}

+

{!local && <>{t("connection.pairing.hub")}: }{target.serverOrigin}

+ {!local &&

{t("connection.pairing.getCode")}

}
{command}
{copied === "unavailable" &&

{t("prov.linkCopyUnavailable")}

} -

{t("connection.pairing.askOperator")}

+ {!local &&

{t("connection.pairing.askOperator")}

}

{t("connection.pairing.notApiKey")}

diff --git a/gui/src/i18n/catalogs.ts b/gui/src/i18n/catalogs.ts index dd5f41b78c4..d64033f412d 100644 --- a/gui/src/i18n/catalogs.ts +++ b/gui/src/i18n/catalogs.ts @@ -8,6 +8,7 @@ import { ru } from "./ru"; import { ja } from "./ja"; import { tr } from "./tr"; import { vi } from "./vi"; +import { pt } from "./pt"; import { LAB_CATALOG_OVERRIDES, type LabLocale } from "./lab-translations"; /** React-free locale catalog registry for formatters and other shared helpers. */ @@ -33,6 +34,7 @@ export const DICTS: Record> = { ja: withLabTranslations("ja", ja), tr: withLabTranslations("tr", tr), vi: withLabTranslations("vi", vi), + pt: withLabTranslations("pt", pt), }; /** Native language names shown by the language picker, kept inside i18n rather than UI metadata. */ diff --git a/gui/src/i18n/de.ts b/gui/src/i18n/de.ts index 0d41a71b036..fc9eee0c806 100644 --- a/gui/src/i18n/de.ts +++ b/gui/src/i18n/de.ts @@ -238,6 +238,10 @@ export const de: Record = { "theme.light": "Hell", "theme.dark": "Dunkel", "theme.system": "System", + "zoom.label": "Zoom", + "zoom.out": "Verkleinern", + "zoom.in": "Vergrößern", + "zoom.reset": "Zoom auf 100 % zurücksetzen", "lang.label": "Sprache", "lang.nativeName": "Deutsch", "provider.name.commandCodeAuth": "Command Code - Auth", @@ -435,14 +439,25 @@ export const de: Record = { "compactionRouting.triggersManual": "Nur manuelles /compact", "compactionRouting.triggersBoth": "Manuell und automatisch", "compactionRouting.triggersAuto": "Nur automatisch", + "compactionRouting.sources": "Quellen", + "compactionRouting.sourcesAll": "Alle Konversationsmodelle", + "compactionRouting.sourcesSelected": "Nur ausgewählte Quellen", + "compactionRouting.sourcesProviders": "Ganze Anbieter", + "compactionRouting.sourcesModels": "Einzelne Modelle", + "compactionRouting.sourcesSaved": "Gespeicherte Selektoren außerhalb des aktuellen Katalogs", + "compactionRouting.sourcesNone": "Wähle mindestens eine Quelle oder wechsle zurück zu allen Konversationsmodellen.", + "compactionRouting.sourcesHint": "Wendet die Überschreibung nur an, wenn das Quellmodell einer Komprimierungsanfrage einem ausgewählten Modell oder provider/* entspricht. Andere Anfragen verwenden weiterhin ihr eigenes Modell.", + "compactionRouting.sourcesGridTitle": "Komprimierungsanfragen umleiten, deren Quellmodell hiermit übereinstimmt:", "compactionRouting.currentModel": "Gesprächsmodell verwenden", "compactionRouting.currentEffort": "Anfrageaufwand beibehalten", "compactionRouting.effortHint": "Der Denkaufwand gilt, wenn der Komprimierungsendpunkt ihn unterstützt. Das Modell muss das gesamte Gespräch verarbeiten können.", "compactionRouting.dataNotice": "Eine abgedeckte Komprimierungsanfrage sendet das gesamte Gespräch zur Zusammenfassung an den Anbieter des gewählten Modells, auch wenn das Gespräch bei einem anderen Anbieter läuft.", "compactionRouting.autoNotice": "Automatische Komprimierung läuft von selbst, daher kann ein langes Gespräch an diesen Anbieter gehen, ohne dass du es angefordert hast.", "compactionRouting.providerWarning": "Mit dieser Einstellung sendet jede abgedeckte Komprimierungsanfrage den vollständigen Gesprächsinhalt zur Zusammenfassung an {provider}.", - "compactionRouting.comboWarning": "Mit dieser Einstellung sendet jede abgedeckte Komprimierungsanfrage den vollständigen Gesprächsinhalt zur Zusammenfassung an die Combo {combo}. Die Combo versucht ihre Ziele ({providers}) der Reihe nach und verwendet das erste, das antwortet, sodass jedes davon das Gespräch erhalten kann.", + "compactionRouting.comboWarning": "Mit dieser Einstellung sendet jede abgedeckte Komprimierungsanfrage den vollständigen Gesprächsinhalt zur Zusammenfassung an die Combo {combo}. Die Combo wählt nach ihrer Routing-Strategie eines ihrer Ziele ({providers}) aus, kann dieselbe Anfrage mit dem vollständigen Gesprächsinhalt nach einem wiederholbaren Fehler aber bei einem anderen Ziel wiederholen, sodass eines oder mehrere Zielanbieter das Gespräch erhalten können.", "compactionRouting.comboProvidersUnknown": "ihre konfigurierten Zielanbieter", + "compactionRouting.providerWarningScoped": "Mit dieser Einstellung senden nur Komprimierungsanfragen, deren Quellmodell {sources} entspricht, den vollständigen Gesprächsinhalt zur Zusammenfassung an {provider}; andere Anfragen verwenden weiterhin ihr eigenes Modell.", + "compactionRouting.comboWarningScoped": "Mit dieser Einstellung senden nur Komprimierungsanfragen, deren Quellmodell {sources} entspricht, den vollständigen Gesprächsinhalt zur Zusammenfassung an die Combo {combo}; andere Anfragen verwenden weiterhin ihr eigenes Modell. Die Combo wählt nach ihrer Routing-Strategie eines ihrer Ziele ({providers}) aus, kann dieselbe Anfrage mit dem vollständigen Gesprächsinhalt nach einem wiederholbaren Fehler aber bei einem anderen Ziel wiederholen, sodass eines oder mehrere Zielanbieter das Gespräch erhalten können.", "compactionRouting.loadFailed": "Komprimierungseinstellungen konnten nicht geladen werden.", "compactionRouting.saved": "Komprimierungseinstellungen gespeichert.", "compactionRouting.saveFailed": "Speichern fehlgeschlagen. Deine Änderungen sind noch vorhanden; versuche es erneut.", @@ -867,6 +882,17 @@ export const de: Record = { "models.allowlistLabel": "Nur ausgewählte", "models.allowlistHint": "Nur geprüfte Modelle gehen in den Katalog (leer = alle). Nützlich für Anbieter mit tausenden Modellen.", "models.selectedCount": "{n} ausgewählt", + "sub.forceTitle": "Alle Subagenten auf ein Modell festlegen", + "sub.forceModel": "Subagentenmodell für Claude Code", + "sub.forceChoose": "Ein freigegebenes Modell auswählen", + "sub.forceHelp": "Gilt für Plugin-Agenten und integrierte Agenten (einschließlich Explore/Plan) und überschreibt die pro Aufruf gewählten Modelle. Forks und Skills mit model: inherit behalten das Modell der Hauptkonversation. Das Hauptmodell und Haiku/small-fast-Sidecars bleiben unverändert.", + "sub.forceScope": "Standardmäßig deaktiviert. Gilt ab dem nächsten Start mit Routing über ocx claude, nicht bei direktem Aufruf von claude. In der Shell exportierte Variablen haben Vorrang; env in settings.json kann sie überschreiben. Die Statusprüfung erfasst nur die CLI und Benutzereinstellungen auf dem Server, keine anderen Shells oder Projekteinstellungen.", + "sub.forceInvalid": "Das konfigurierte Ziel ist nicht verfügbar. Die Modellüberschreibung beim Start wird übersprungen. Wählen Sie ein freigegebenes Modell oder deaktivieren Sie diese Funktion.", + "sub.forceOld": "Claude Code ist älter als 2.1.257: FORCE wird ignoriert; es gilt nur das nicht erzwungene Standardmodell für Subagenten.", + "sub.forceUnknown": "Die Version von Claude Code ist unbekannt. Das Erzwingen eines Modells erfordert Version 2.1.257 oder neuer.", + "sub.forceOverride": "Durch settings.json überschrieben: env enthält dort ein Subagentenmodell oder eine FORCE-Einstellung. OpenCodex verändert diese Datei nicht.", + "sub.forceSettingsUnknown": "settings.json konnte nicht geprüft werden. Ob Einstellungen diese Vorgabe überschreiben, ist unbekannt.", + "sub.forceSaved": "Gespeichert. Wird beim nächsten Start mit Routing über ocx claude wirksam.", "sub.subtitle": "Codex {cmd} bewirbt nur die ersten 5 Modelle (nach Priorität) als Overrides. Wähle hier bis zu 5 — natives gpt oder geroutet — und opencodex setzt ihre Katalog-Priorität, sodass genau diese führen. Jedes andere Modell bleibt über seinen exakten Namen aufrufbar; dies steuert nur die Anzeige.", "sub.featured": "Empfohlen", "sub.advanced": "Erweitert", @@ -1011,7 +1037,7 @@ export const de: Record = { "logs.detail.ttft": "TTFT", "logs.detail.decodeTokPerSec": "Dekodierrate (geschätzt)", "logs.detail.reason.ttft_missing": "Für diese Anfrage wurde keine Zeit bis zum ersten Token erfasst, es gibt also kein Dekodierfenster zum Messen.", - "logs.detail.reason.decode_window_too_short": "Das Fenster nach dem ersten Token lag unter einer Sekunde und ist zu kurz für eine Schätzung der Dekodierrate.", + "logs.detail.reason.decode_window_too_short": "Das gemessene Ausgabefenster lag unter einer Sekunde und ist zu kurz für eine Schätzung der Dekodierrate.", "logs.detail.costTotal": "Listenpreis-Äquivalent", "logs.detail.totalTokens": "Tokens gesamt", "logs.detail.matchedKey": "Zugeordneter Preisschlüssel", @@ -1267,9 +1293,12 @@ export const de: Record = { "usage.col.reported": "Gemeldet", "usage.col.inputTokens": "Eingabe-Tokens", "usage.col.outputTokens": "Ausgabe-Tokens", + "usage.col.tokPerSec": "Ausgabe tok/s", "usage.col.cacheHits": "Cache-Treffer", "usage.col.cacheWrites": "Cache-Schreibvorgänge", "usage.col.cacheHitRate": "Trefferquote", + "usage.throughput.title": "End-to-End-Ausgabedurchsatz: Summe der Ausgabe-Tokens geteilt durch die Summe der Wanduhrzeiten über {samples} gemessene Versuche. Enthält die Wartezeit vor der Dekodierung und liegt daher unter der stabilen Dekodiergeschwindigkeit; Anfragen ohne gemessene Tokens oder Dauer werden ausgeschlossen.", + "usage.throughput.unmeasured": "Keine Anfrage dieser Zeile hat sowohl Ausgabe-Tokens als auch Dauer gemeldet, daher gibt es keinen Durchschnitt.", "usage.cacheHitRate.partial": "Gemittelt über {measured} von insgesamt {total} Eingabe-Tokens; für die übrigen Anfragen wurden keine Cache-Details gemeldet.", "usage.cacheHitRate.unmeasured": "Für keine Anfrage in dieser Zeile wurden Cache-Details gemeldet, daher kann keine durchschnittliche Trefferquote berechnet werden.", "usage.unavailable": "—", @@ -1483,6 +1512,16 @@ export const de: Record = { "integrations.tab.cline": "Cline CLI", "integrations.tab.kilo": "Kilo", "integrations.tab.droid": "Factory Droid", + "integrations.droidReasoning.title": "Standard für Reasoning-Aufwand", + "integrations.droidReasoning.description": "Der Standard gilt nur, wenn eine Anfrage keinen Reasoning-Aufwand angibt. Deaktiviere Factory Droid, um diese gespeicherten Standards zu entfernen.", + "integrations.droidReasoning.noDefault": "Kein Standard", + "integrations.droidReasoning.noEfforts": "Keine Optionen für die Reasoning-Stärke verfügbar", + "integrations.droidReasoning.noModels": "Keine exportierten Modelle verfügbar.", + "integrations.droidReasoning.modelDefault": "Standard-Reasoning-Aufwand für {model}", + "integrations.droidReasoning.unsupportedTitle": "Einige gespeicherte Standards werden nicht mehr unterstützt. Entferne sie; speichere und prüfe anschließend die Änderungen, um sie anzuwenden.", + "integrations.droidReasoning.unsupportedEffort": "Nicht unterstützter Aufwand: {effort}", + "integrations.droidReasoning.clear": "Standard entfernen", + "integrations.droidReasoning.saveReview": "Änderungen speichern / prüfen", "integrations.aside.profilesTitle": "Aside-Profile", "integrations.aside.profilesHint": "Wähle, welche Profile die ausgewählten Modelle erhalten. Das aktive Aside-Profil bleibt unverändert.", "integrations.aside.all": "Alle Profile synchronisieren", @@ -3559,6 +3598,15 @@ export const de: Record = { "link.addChild": "Kind hinzufügen", "link.sheetTitle": "Kindercomputer hinzufügen", "link.candidates": "SSH-Hosts", + "link.setup.relationship": "Der Child nutzt die OpenCodex-Anbieter dieses Home über SSH.", + "link.setup.requirements": "Erfordert schlüsselbasiertes SSH von Home zu Child, OpenCodex 2.66.0+ auf dem Child und macOS oder Linux auf beiden Computern.", + "link.setup.emptyHome": "Aliase stammen aus ~/.ssh/config dieses Home. Fügen Sie einen Host-Eintrag hinzu und suchen Sie erneut, oder geben Sie unten einen bestehenden Alias ein.", + "link.setup.emptyLocal": "Aliase stammen aus ~/.ssh/config dieses Computers. Fügen Sie einen Host-Eintrag hinzu und suchen Sie erneut, oder geben Sie unten einen bestehenden Alias ein.", + "link.setup.guide": "Remote-Link-Einrichtungsanleitung", + "link.discoveryFailed": "SSH-Hosts konnten nicht geladen werden", + "link.rescan": "Hosts erneut suchen", + "link.aliasRequired": "Wählen oder geben Sie einen Alias ein, um den Verbindungstest zu aktivieren.", + "link.probeHelp": "Prüfen Sie den Grund oben. Bei SSH-Zugriffsproblemen führen Sie dies im Terminal dieses Computers aus, ersetzen Sie durch Ihren SSH-Alias und versuchen Sie es erneut.", "link.noCandidates": "Keine SSH-Hostkandidaten gefunden.", "link.alias": "SSH-Host-Alias", "link.aliasPlaceholder": "Alias aus der SSH-Konfiguration eingeben", @@ -3595,6 +3643,8 @@ export const de: Record = { "remoteLink.error.join_in_progress": "Die Remote-Link-Anfrage konnte nicht abgeschlossen werden.", "remoteLink.error.join_port_failed": "Die Remote-Link-Anfrage konnte nicht abgeschlossen werden.", "remoteLink.error.join_port_mismatch": "OpenCodex läuft nicht auf seinem konfigurierten Port und kann daher nicht als Kind neu starten. Starten Sie OpenCodex auf dem konfigurierten Port neu und versuchen Sie es dann erneut.", + "remoteLink.joinDenied.pairing_required": "Koppeln Sie diesen Computer zuerst, um ihn als Kind zu verbinden. Öffnen Sie das Dashboard dieses Computers über seine Loopback-Adresse und geben Sie einen vom Betreiber erstellten einmaligen Kopplungscode ein.", + "remoteLink.joinDenied.unavailable": "Eine Verbindung als Kind ist in dieser Dashboard-Sitzung nicht verfügbar. Aktualisieren Sie den Status und prüfen Sie die Kopplungs- und Laufzeiteinstellungen dieses Computers.", "remoteLink.error.join_rollback_failed": "Die Verbindung ist fehlgeschlagen und der Link auf Home konnte nicht entfernt werden. Wiederholen Sie die Bereinigung oder führen Sie auf Home ocx link revoke aus.", "remoteLink.error.join_restart_failed": "Der Link ist bereit. Starten Sie OpenCodex auf diesem Computer neu, um die Verbindung als Child abzuschließen.", "remoteLink.error.join_connect_failed": "Die Remote-Link-Anfrage konnte nicht abgeschlossen werden.", diff --git a/gui/src/i18n/en.ts b/gui/src/i18n/en.ts index b71168448c5..045601993e1 100644 --- a/gui/src/i18n/en.ts +++ b/gui/src/i18n/en.ts @@ -176,6 +176,10 @@ export const en = { "theme.light": "Light", "theme.dark": "Dark", "theme.system": "System", + "zoom.label": "Zoom", + "zoom.out": "Zoom out", + "zoom.in": "Zoom in", + "zoom.reset": "Reset zoom to 100%", "lang.label": "Language", "lang.nativeName": "English", "provider.name.commandCodeAuth": "Command Code - Auth", @@ -453,14 +457,25 @@ export const en = { "compactionRouting.triggersManual": "Manual /compact only", "compactionRouting.triggersBoth": "Manual and automatic", "compactionRouting.triggersAuto": "Automatic only", + "compactionRouting.sources": "Sources", + "compactionRouting.sourcesAll": "All conversation models", + "compactionRouting.sourcesSelected": "Selected sources only", + "compactionRouting.sourcesProviders": "Entire providers", + "compactionRouting.sourcesModels": "Individual models", + "compactionRouting.sourcesSaved": "Saved selectors not in the current catalog", + "compactionRouting.sourcesNone": "Select at least one source, or switch back to all conversation models.", + "compactionRouting.sourcesHint": "Apply the override only when a compaction request's source model matches a selected model or provider/*. Other requests keep using their own model.", + "compactionRouting.sourcesGridTitle": "Reroute compaction requests whose source model matches:", "compactionRouting.currentModel": "Use conversation model", "compactionRouting.currentEffort": "Keep request effort", "compactionRouting.effortHint": "Reasoning applies where supported by the compaction endpoint. The model must accept the full conversation.", "compactionRouting.dataNotice": "A covered compaction request sends the entire conversation to the selected model's provider for summarization, even when the conversation runs on another provider.", "compactionRouting.autoNotice": "Automatic compaction runs on its own, so a long conversation can be sent to that provider without you asking for it.", "compactionRouting.providerWarning": "With this setting, every covered compaction request sends the full conversation contents to {provider} for summarization.", - "compactionRouting.comboWarning": "With this setting, every covered compaction request sends the full conversation contents to combo {combo} for summarization. The combo attempts its targets ({providers}) in order and uses the first that answers, so any one of them can receive the conversation.", + "compactionRouting.comboWarning": "With this setting, every covered compaction request sends the full conversation contents to combo {combo} for summarization. The combo picks one of its targets ({providers}) by its routing strategy, but after a retryable failure it may retry the same full-conversation request on another target, so one or more target providers can receive the conversation.", "compactionRouting.comboProvidersUnknown": "its configured target providers", + "compactionRouting.providerWarningScoped": "With this setting, only compaction requests whose source model matches {sources} send the full conversation contents to {provider} for summarization; other requests keep using their own model.", + "compactionRouting.comboWarningScoped": "With this setting, only compaction requests whose source model matches {sources} send the full conversation contents to combo {combo} for summarization; other requests keep using their own model. The combo picks one of its targets ({providers}) by its routing strategy, but after a retryable failure it may retry the same full-conversation request on another target, so one or more target providers can receive the conversation.", "compactionRouting.loadFailed": "Could not load compaction settings.", "compactionRouting.saved": "Compaction settings saved.", "compactionRouting.saveFailed": "Could not save. Your changes are still here; try again.", @@ -899,6 +914,17 @@ export const en = { "models.selectedCount": "{n} selected", // subagents + "sub.forceTitle": "Force all subagents onto one model", + "sub.forceModel": "Claude Code subagent model", + "sub.forceChoose": "Choose an exposed model", + "sub.forceHelp": "Applies to plugin and built-in agents (including Explore/Plan), overriding per-call models. Forks and skills with model: inherit keep the main conversation model. The main model and Haiku/small-fast sidecars are unaffected.", + "sub.forceScope": "Default off. Applies from the next routed ocx claude launch, not plain claude. Shell exports win; settings.json env can override them. Status checks the server’s CLI and user settings only, not another shell or project settings.", + "sub.forceInvalid": "The configured target is unavailable. Launch override will be skipped; select an exposed model or turn this off.", + "sub.forceOld": "Claude Code is older than 2.1.257: FORCE is ignored; only the non-forced subagent default applies.", + "sub.forceUnknown": "Claude Code version is unknown. Force support requires version 2.1.257 or newer.", + "sub.forceOverride": "Overridden by settings.json: its env contains a subagent model or FORCE setting. OpenCodex does not modify it.", + "sub.forceSettingsUnknown": "Could not inspect settings.json. Settings override status is unknown.", + "sub.forceSaved": "Saved. Takes effect on the next routed ocx claude launch.", "sub.subtitle": "Codex's {cmd} advertises only the first 5 models (by priority) as overrides. Pick up to 5 here — native gpt or routed — and opencodex sets their catalog priority so exactly these lead. Any other model is still callable by its exact name; this only controls what's shown.", "sub.featured": "Featured", "sub.advanced": "Advanced", @@ -1069,7 +1095,7 @@ export const en = { "logs.detail.ttft": "TTFT", "logs.detail.decodeTokPerSec": "Decode rate (est.)", "logs.detail.reason.ttft_missing": "No time-to-first-token was recorded for this request, so there is no decode window to measure.", - "logs.detail.reason.decode_window_too_short": "The window after the first token was under a second, which is too short to estimate a decode rate from.", + "logs.detail.reason.decode_window_too_short": "The measured output window was under a second, which is too short to estimate a decode rate from.", "logs.detail.costTotal": "List-price equivalent", "logs.detail.totalTokens": "Total tokens", "logs.detail.matchedKey": "Matched price key", @@ -1329,9 +1355,12 @@ export const en = { "usage.col.reported": "Reported", "usage.col.inputTokens": "Input tokens", "usage.col.outputTokens": "Output tokens", + "usage.col.tokPerSec": "Output tok/s", "usage.col.cacheHits": "Cache hits", "usage.col.cacheWrites": "Cache writes", "usage.col.cacheHitRate": "Hit rate", + "usage.throughput.title": "End-to-end output throughput: summed output tokens over summed wall-clock duration across {samples} measured attempt(s). It includes pre-decode wait time, so it runs below steady-state decode speed; requests without measured tokens or duration are excluded.", + "usage.throughput.unmeasured": "No request for this row reported both output tokens and duration, so there is no throughput to average.", "usage.cacheHitRate.partial": "Averaged over {measured} of {total} input tokens; the remaining requests reported no cache detail.", "usage.cacheHitRate.unmeasured": "No request for this row reported cache detail, so there is no hit rate to average.", "usage.unavailable": "—", @@ -2049,6 +2078,16 @@ export const en = { "integrations.tab.cline": "Cline CLI", "integrations.tab.kilo": "Kilo", "integrations.tab.droid": "Factory Droid", + "integrations.droidReasoning.title": "Default reasoning effort", + "integrations.droidReasoning.description": "A default is used only when a request omits its reasoning effort. Disable Factory Droid to remove these saved defaults.", + "integrations.droidReasoning.noDefault": "No default", + "integrations.droidReasoning.noEfforts": "No reasoning-effort options available", + "integrations.droidReasoning.noModels": "No exported models are available.", + "integrations.droidReasoning.modelDefault": "Default reasoning effort for {model}", + "integrations.droidReasoning.unsupportedTitle": "Some saved defaults are no longer supported. Clear them, then save / review changes to apply.", + "integrations.droidReasoning.unsupportedEffort": "Unsupported effort: {effort}", + "integrations.droidReasoning.clear": "Clear default", + "integrations.droidReasoning.saveReview": "Save / review changes", "integrations.aside.profilesTitle": "Aside profiles", "integrations.aside.profilesHint": "Choose which profiles receive the selected models. Aside’s active profile stays unchanged.", "integrations.aside.all": "Sync all profiles", @@ -3593,6 +3632,15 @@ export const en = { "link.addChild": "Add child", "link.sheetTitle": "Add a child computer", "link.candidates": "SSH hosts", + "link.setup.relationship": "The Child will use this Home's OpenCodex providers through SSH.", + "link.setup.requirements": "Requires key-based SSH from Home to Child, OpenCodex 2.66.0+ on the Child, and macOS or Linux on both computers.", + "link.setup.emptyHome": "Aliases come from this Home's ~/.ssh/config. Add a Host entry, then rescan, or enter an existing alias below.", + "link.setup.emptyLocal": "Aliases come from this computer's ~/.ssh/config. Add a Host entry, then rescan, or enter an existing alias below.", + "link.setup.guide": "Remote Link setup guide", + "link.discoveryFailed": "Could not load SSH hosts", + "link.rescan": "Rescan hosts", + "link.aliasRequired": "Select or enter an alias to enable Test connection.", + "link.probeHelp": "Check the reason above. For SSH access problems, run this in a terminal on this computer, replacing with your SSH alias, then retry.", "link.noCandidates": "No SSH host candidates were found.", "link.alias": "SSH host alias", "link.aliasPlaceholder": "Enter an alias from your SSH config", @@ -3629,6 +3677,8 @@ export const en = { "remoteLink.error.join_in_progress": "Remote link request could not be completed.", "remoteLink.error.join_port_failed": "Remote link request could not be completed.", "remoteLink.error.join_port_mismatch": "OpenCodex is not running on its configured port, so it cannot restart as a Child. Restart OpenCodex on its configured port, then try again.", + "remoteLink.joinDenied.pairing_required": "Pair this machine first to join as a Child. Open this computer's loopback dashboard and enter an operator-created one-use pairing code.", + "remoteLink.joinDenied.unavailable": "Joining as a Child is unavailable for this dashboard session. Refresh the status and check this machine's pairing and runtime settings.", "remoteLink.error.join_rollback_failed": "Joining failed and the link on Home could not be removed. Retry the cleanup, or run ocx link revoke on Home.", "remoteLink.error.join_restart_failed": "The link is ready. Restart OpenCodex on this computer to finish connecting as a Child.", "remoteLink.error.join_connect_failed": "Remote link request could not be completed.", diff --git a/gui/src/i18n/fr.ts b/gui/src/i18n/fr.ts index 01b2f499219..eb629193027 100644 --- a/gui/src/i18n/fr.ts +++ b/gui/src/i18n/fr.ts @@ -173,6 +173,10 @@ export const fr: Record = { "theme.light": "Clair", "theme.dark": "Sombre", "theme.system": "Système", + "zoom.label": "Zoom", + "zoom.out": "Zoom arrière", + "zoom.in": "Zoom avant", + "zoom.reset": "Réinitialiser le zoom à 100 %", "lang.label": "Langue", "lang.nativeName": "Français", "provider.name.commandCodeAuth": "Command Code - Auth", @@ -443,14 +447,25 @@ export const fr: Record = { "compactionRouting.triggersManual": "/compact manuel uniquement", "compactionRouting.triggersBoth": "Manuelle et automatique", "compactionRouting.triggersAuto": "Automatique uniquement", + "compactionRouting.sources": "Sources concernées", + "compactionRouting.sourcesAll": "Tous les modèles de conversation", + "compactionRouting.sourcesSelected": "Sources sélectionnées uniquement", + "compactionRouting.sourcesProviders": "Fournisseurs entiers", + "compactionRouting.sourcesModels": "Modèles individuels", + "compactionRouting.sourcesSaved": "Sélecteurs enregistrés absents du catalogue actuel", + "compactionRouting.sourcesNone": "Sélectionnez au moins une source, ou revenez à tous les modèles de conversation.", + "compactionRouting.sourcesHint": "Applique le remplacement uniquement si le modèle source de la requête de compaction correspond à un modèle choisi ou à provider/*. Les autres requêtes gardent leur propre modèle.", + "compactionRouting.sourcesGridTitle": "Rerouter les requêtes de compaction dont le modèle source correspond à :", "compactionRouting.currentModel": "Utiliser le modèle de la conversation", "compactionRouting.currentEffort": "Conserver l’effort de la requête", "compactionRouting.effortHint": "Le raisonnement s’applique si le point de terminaison de compaction le prend en charge. Le modèle doit accepter toute la conversation.", "compactionRouting.dataNotice": "Une requête de compaction couverte envoie toute la conversation au fournisseur du modèle choisi pour la résumer, même si la conversation s’exécute chez un autre fournisseur.", "compactionRouting.autoNotice": "La compaction automatique se déclenche d’elle-même : une longue conversation peut donc partir chez ce fournisseur sans que vous l’ayez demandé.", "compactionRouting.providerWarning": "Avec ce réglage, chaque requête de compaction couverte envoie l’intégralité du contenu de la conversation à {provider} pour la résumer.", - "compactionRouting.comboWarning": "Avec ce réglage, chaque requête de compaction couverte envoie l’intégralité du contenu de la conversation au combo {combo} pour le résumer. Le combo essaie ses cibles ({providers}) dans l’ordre et retient la première qui répond, donc n’importe laquelle peut recevoir la conversation.", + "compactionRouting.comboWarning": "Avec ce réglage, chaque requête de compaction couverte envoie l’intégralité du contenu de la conversation au combo {combo} pour le résumer. Le combo choisit l’une de ses cibles ({providers}) selon sa stratégie de routage, mais après un échec réessayable il peut réessayer la même requête avec l’intégralité de la conversation sur une autre cible, donc une ou plusieurs cibles peuvent recevoir la conversation.", "compactionRouting.comboProvidersUnknown": "ses fournisseurs cibles configurés", + "compactionRouting.providerWarningScoped": "Avec ce réglage, seules les requêtes de compaction dont le modèle source correspond à {sources} envoient l’intégralité de la conversation à {provider} pour la résumer ; les autres requêtes gardent leur propre modèle.", + "compactionRouting.comboWarningScoped": "Avec ce réglage, seules les requêtes de compaction dont le modèle source correspond à {sources} envoient l’intégralité de la conversation au combo {combo} pour la résumer ; les autres requêtes gardent leur propre modèle. Le combo choisit l’une de ses cibles ({providers}) selon sa stratégie de routage, mais après un échec réessayable il peut réessayer la même requête avec l’intégralité de la conversation sur une autre cible, donc une ou plusieurs cibles peuvent recevoir la conversation.", "compactionRouting.loadFailed": "Impossible de charger les paramètres de compaction.", "compactionRouting.saved": "Paramètres de compaction enregistrés.", "compactionRouting.saveFailed": "Échec de l’enregistrement. Vos modifications sont conservées ; réessayez.", @@ -882,6 +897,17 @@ export const fr: Record = { "models.allowlistLabel": "Sélection uniquement", "models.allowlistHint": "Seuls les modèles cochés sont inclus dans le catalogue (vide = tous). Utile pour les fournisseurs proposant des milliers de modèles.", "models.selectedCount": "{n} sélectionnés", + "sub.forceTitle": "Imposer un même modèle à tous les sous-agents", + "sub.forceModel": "Modèle des sous-agents Claude Code", + "sub.forceChoose": "Choisir un modèle exposé", + "sub.forceHelp": "S’applique aux agents des plugins et aux agents intégrés (y compris Explore/Plan), en remplaçant les modèles choisis à chaque appel. Les forks et les skills avec model: inherit conservent le modèle de la conversation principale. Le modèle principal et les modèles auxiliaires Haiku/small-fast ne sont pas affectés.", + "sub.forceScope": "Désactivé par défaut. S’applique au prochain lancement avec routage via ocx claude, pas à un lancement direct de claude. Les variables exportées dans le shell sont prioritaires ; env dans settings.json peut les remplacer. L’état vérifie uniquement la CLI et les paramètres utilisateur du serveur, pas ceux d’un autre shell ni les paramètres du projet.", + "sub.forceInvalid": "La cible configurée n’est pas disponible. Le remplacement du modèle au lancement sera ignoré ; choisissez un modèle exposé ou désactivez cette option.", + "sub.forceOld": "La version de Claude Code est antérieure à 2.1.257 : FORCE est ignoré ; seul le modèle par défaut non imposé des sous-agents s’applique.", + "sub.forceUnknown": "La version de Claude Code est inconnue. L’imposition d’un modèle nécessite la version 2.1.257 ou ultérieure.", + "sub.forceOverride": "Remplacé par settings.json : sa section env contient un modèle de sous-agent ou un paramètre FORCE. OpenCodex ne modifie pas ce fichier.", + "sub.forceSettingsUnknown": "Impossible d’examiner settings.json. On ne sait pas si ses paramètres remplacent ce réglage.", + "sub.forceSaved": "Enregistré. Prendra effet au prochain lancement avec routage via ocx claude.", "sub.subtitle": "La commande {cmd} de Codex ne présente que les 5 premiers modèles (par priorité) comme remplacements. Choisissez-en jusqu’à 5 ici — natifs gpt ou routés — et opencodex définit leur priorité dans le catalogue pour qu’ils apparaissent en tête. Tout autre modèle reste accessible par son nom exact ; ceci contrôle uniquement ce qui est affiché.", "sub.featured": "À la une", "sub.advanced": "Avancé", @@ -1050,7 +1076,7 @@ export const fr: Record = { "logs.detail.ttft": "TTFT", "logs.detail.decodeTokPerSec": "Débit de décodage (est.)", "logs.detail.reason.ttft_missing": "Aucun délai jusqu’au premier token n’a été enregistré pour cette requête, il n’y a donc pas de fenêtre de décodage à mesurer.", - "logs.detail.reason.decode_window_too_short": "La fenêtre après le premier token durait moins d’une seconde, ce qui est trop court pour estimer un débit de décodage.", + "logs.detail.reason.decode_window_too_short": "La fenêtre de sortie mesurée durait moins d’une seconde, ce qui est trop court pour estimer un débit de décodage.", "logs.detail.costTotal": "Équivalent au tarif catalogue", "logs.detail.totalTokens": "Nombre total de jetons", "logs.detail.matchedKey": "Clé de tarif correspondante", @@ -1306,9 +1332,12 @@ export const fr: Record = { "usage.col.reported": "Communiquées", "usage.col.inputTokens": "Jetons d’entrée", "usage.col.outputTokens": "Jetons de sortie", + "usage.col.tokPerSec": "Sortie tok/s", "usage.col.cacheHits": "Lectures du cache", "usage.col.cacheWrites": "Écritures dans le cache", "usage.col.cacheHitRate": "Taux de succès du cache", + "usage.throughput.title": "Débit de sortie de bout en bout : somme des jetons de sortie divisée par la somme des durées horloge sur {samples} tentative(s) mesurée(s). Il inclut l'attente avant décodage, donc il reste sous la vitesse de décodage stable ; les requêtes sans jetons mesurés ou sans durée mesurée sont exclues.", + "usage.throughput.unmeasured": "Aucune requête de cette ligne n'a rapporté à la fois des jetons de sortie et une durée ; aucun débit à moyenner.", "usage.cacheHitRate.partial": "Moyenne calculée sur {measured} des {total} jetons d’entrée ; les autres requêtes n’ont fourni aucun détail sur le cache.", "usage.cacheHitRate.unmeasured": "Aucune requête de cette ligne n’a fourni de détails sur le cache ; il n’y a donc pas de taux de succès moyen à calculer.", "usage.unavailable": "—", @@ -2021,6 +2050,16 @@ export const fr: Record = { "integrations.tab.cline": "Cline CLI", "integrations.tab.kilo": "Kilo", "integrations.tab.droid": "Factory Droid", + "integrations.droidReasoning.title": "Niveau de raisonnement par défaut", + "integrations.droidReasoning.description": "Le niveau par défaut s’applique uniquement si la requête ne précise aucun niveau de raisonnement. Désactivez Factory Droid pour supprimer ces valeurs enregistrées.", + "integrations.droidReasoning.noDefault": "Aucune valeur par défaut", + "integrations.droidReasoning.noEfforts": "Aucune option de niveau de raisonnement disponible", + "integrations.droidReasoning.noModels": "Aucun modèle exporté n’est disponible.", + "integrations.droidReasoning.modelDefault": "Niveau de raisonnement par défaut pour {model}", + "integrations.droidReasoning.unsupportedTitle": "Certaines valeurs enregistrées ne sont plus prises en charge. Supprimez-les, puis enregistrez / vérifiez les changements pour les appliquer.", + "integrations.droidReasoning.unsupportedEffort": "Niveau non pris en charge : {effort}", + "integrations.droidReasoning.clear": "Supprimer la valeur", + "integrations.droidReasoning.saveReview": "Enregistrer / vérifier les changements", "integrations.aside.profilesTitle": "Profils Aside", "integrations.aside.profilesHint": "Choisissez les profils qui recevront les modèles sélectionnés. Le profil actif dans Aside reste inchangé.", "integrations.aside.all": "Synchroniser tous les profils", @@ -3548,6 +3587,15 @@ export const fr: Record = { "link.addChild": "Ajouter un enfant", "link.sheetTitle": "Ajouter un ordinateur enfant", "link.candidates": "Hôtes SSH", + "link.setup.relationship": "Le Child utilisera les fournisseurs OpenCodex de ce Home via SSH.", + "link.setup.requirements": "Nécessite SSH par clé de Home vers Child, OpenCodex 2.66.0+ sur Child et macOS ou Linux sur les deux ordinateurs.", + "link.setup.emptyHome": "Les alias proviennent de ~/.ssh/config sur ce Home. Ajoutez une entrée Host puis relancez la recherche, ou saisissez un alias existant ci-dessous.", + "link.setup.emptyLocal": "Les alias proviennent de ~/.ssh/config sur cet ordinateur. Ajoutez une entrée Host puis relancez la recherche, ou saisissez un alias existant ci-dessous.", + "link.setup.guide": "Guide de configuration Remote Link", + "link.discoveryFailed": "Impossible de charger les hôtes SSH", + "link.rescan": "Rechercher les hôtes", + "link.aliasRequired": "Sélectionnez ou saisissez un alias pour activer le test de connexion.", + "link.probeHelp": "Vérifiez la raison ci-dessus. En cas de problème d’accès SSH, exécutez ceci dans un terminal sur cet ordinateur en remplaçant par votre alias SSH, puis réessayez.", "link.noCandidates": "Aucun hôte SSH candidat trouvé.", "link.alias": "Alias de l’hôte SSH", "link.aliasPlaceholder": "Saisissez un alias de votre configuration SSH", @@ -3586,6 +3634,8 @@ export const fr: Record = { "remoteLink.error.join_restart_failed": "Le lien est prêt. Redémarrez OpenCodex sur cet ordinateur pour terminer la connexion en tant qu’Enfant.", "remoteLink.error.join_port_failed": "La demande de lien distant n’a pas pu aboutir.", "remoteLink.error.join_port_mismatch": "OpenCodex ne tourne pas sur son port configuré et ne peut donc pas redémarrer comme Enfant. Redémarrez OpenCodex sur son port configuré, puis réessayez.", + "remoteLink.joinDenied.pairing_required": "Associez d’abord cet ordinateur pour le connecter en tant qu’Enfant. Ouvrez son tableau de bord via son adresse de bouclage et saisissez un code d’association à usage unique créé par l’opérateur.", + "remoteLink.joinDenied.unavailable": "La connexion en tant qu’Enfant n’est pas disponible dans cette session du tableau de bord. Actualisez l’état et vérifiez les paramètres d’association et d’exécution de cet ordinateur.", "remoteLink.error.join_connect_failed": "La demande de lien distant n’a pas pu aboutir.", "link.noChildren": "Aucun ordinateur enfant connecté.", "remoteLink.status.connecting": "Connexion", diff --git a/gui/src/i18n/ja.ts b/gui/src/i18n/ja.ts index e3f01974249..a298a02b15e 100644 --- a/gui/src/i18n/ja.ts +++ b/gui/src/i18n/ja.ts @@ -243,6 +243,10 @@ export const ja: Record = { "theme.light": "ライト", "theme.dark": "ダーク", "theme.system": "システム", + "zoom.label": "表示倍率", + "zoom.out": "縮小", + "zoom.in": "拡大", + "zoom.reset": "倍率を100%に戻す", "lang.label": "言語", "lang.nativeName": "日本語", "provider.name.commandCodeAuth": "Command Code - Auth", @@ -444,14 +448,25 @@ export const ja: Record = { "compactionRouting.triggersManual": "手動の /compact のみ", "compactionRouting.triggersBoth": "手動と自動", "compactionRouting.triggersAuto": "自動のみ", + "compactionRouting.sources": "対象ソース", + "compactionRouting.sourcesAll": "すべての会話モデル", + "compactionRouting.sourcesSelected": "選択したソースのみ", + "compactionRouting.sourcesProviders": "プロバイダー全体", + "compactionRouting.sourcesModels": "個別モデル", + "compactionRouting.sourcesSaved": "現在のカタログにない保存済みセレクター", + "compactionRouting.sourcesNone": "ソースを少なくとも 1 つ選択するか、すべての会話モデルに戻してください。", + "compactionRouting.sourcesHint": "圧縮リクエストの元モデルが選択したモデルまたは provider/* に一致する場合だけ上書きします。一致しないリクエストは元のモデルを使います。", + "compactionRouting.sourcesGridTitle": "元モデルが以下に一致する圧縮リクエストをリルートします:", "compactionRouting.currentModel": "会話のモデルを使用", "compactionRouting.currentEffort": "リクエストの推論強度を維持", "compactionRouting.effortHint": "圧縮エンドポイントが対応している場合に推論設定が適用されます。モデルは会話全体を受け入れられる必要があります。", "compactionRouting.dataNotice": "適用対象の圧縮リクエストは、会話が別のプロバイダーで動いていても、会話全体を選択したモデルのプロバイダーへ送信して要約します。", "compactionRouting.autoNotice": "自動圧縮はユーザーが求めなくても実行されるため、長い会話が知らないうちにそのプロバイダーへ送信されることがあります。", "compactionRouting.providerWarning": "この設定では、適用対象の圧縮リクエストのたびに会話の全内容が要約のために {provider} へ送信されます。", - "compactionRouting.comboWarning": "この設定では、適用対象の圧縮リクエストのたびに会話の全内容が要約のためコンボ {combo} へ送信されます。コンボはターゲット({providers})を順に試し、最初に応答したものを使うため、そのいずれもが会話を受け取る可能性があります。", + "compactionRouting.comboWarning": "この設定では、適用対象の圧縮リクエストのたびに会話の全内容が要約のためコンボ {combo} へ送信されます。コンボはルーティング戦略に従ってターゲット({providers})のいずれかを選びますが、再試行可能な失敗の後には同じ会話全内容のリクエストを別のターゲットで再試行する可能性があるため、ターゲットのうち 1 つ以上が会話を受け取る可能性があります。", "compactionRouting.comboProvidersUnknown": "設定済みのターゲットプロバイダー", + "compactionRouting.providerWarningScoped": "この設定では、元モデルが {sources} に一致する圧縮リクエストだけが会話の全内容を要約のため {provider} へ送信します。一致しないリクエストは元のモデルを使います。", + "compactionRouting.comboWarningScoped": "この設定では、元モデルが {sources} に一致する圧縮リクエストだけが会話の全内容を要約のためコンボ {combo} へ送信します。一致しないリクエストは元のモデルを使います。コンボはルーティング戦略に従ってターゲット({providers})のいずれかを選びますが、再試行可能な失敗の後には同じ会話全内容のリクエストを別のターゲットで再試行する可能性があるため、ターゲットのうち 1 つ以上が会話を受け取る可能性があります。", "compactionRouting.loadFailed": "圧縮設定を読み込めませんでした。", "compactionRouting.saved": "圧縮設定を保存しました。", "compactionRouting.saveFailed": "保存できませんでした。変更内容は保持されています。再試行してください。", @@ -806,6 +821,17 @@ export const ja: Record = { "models.selectedCount": "{n} 件選択", // subagents + "sub.forceTitle": "すべてのサブエージェントに同じモデルを強制適用", + "sub.forceModel": "Claude Code サブエージェントのモデル", + "sub.forceChoose": "公開済みのモデルを選択", + "sub.forceHelp": "プラグインおよび組み込みエージェント(Explore/Plan を含む)に適用され、呼び出しごとのモデル指定を上書きします。フォークと model: inherit を指定したスキルは、メイン会話のモデルを引き続き使用します。メインモデルと Haiku/small-fast のサイドカーには影響しません。", + "sub.forceScope": "既定では無効です。次回ルーティング経由で ocx claude を起動したときから適用され、claude の直接起動には適用されません。シェルで export した環境変数が優先されますが、settings.json の env でさらに上書きできます。ステータス確認の対象はサーバー上の CLI とユーザー設定のみで、別のシェルやプロジェクト設定は含まれません。", + "sub.forceInvalid": "設定された対象は利用できません。起動時のモデル上書きをスキップします。公開済みのモデルを選択するか、この機能を無効にしてください。", + "sub.forceOld": "Claude Code のバージョンが 2.1.257 より古いため、FORCE は無視され、強制ではないサブエージェントの既定モデルのみが適用されます。", + "sub.forceUnknown": "Claude Code のバージョンが不明です。モデルの強制適用にはバージョン 2.1.257 以降が必要です。", + "sub.forceOverride": "settings.json によって上書きされています。env にサブエージェントのモデルまたは FORCE の設定が含まれています。OpenCodex はこのファイルを変更しません。", + "sub.forceSettingsUnknown": "settings.json を確認できませんでした。設定による上書きの有無は不明です。", + "sub.forceSaved": "保存しました。次回ルーティング経由で ocx claude を起動したときに反映されます。", "sub.subtitle": "Codex の {cmd} は最初の 5 モデル(優先度順)のみをオーバーライドとして通知します。ここで最大 5 つを選んでください — ネイティブ gpt またはルーティング — opencodex がカタログ優先度を設定し、これらが先頭に来るようにします。他のモデルも正確な名前で呼び出し可能です; これは表示のみを制御します。", "sub.featured": "おすすめ", "sub.advanced": "詳細設定", @@ -952,7 +978,7 @@ export const ja: Record = { "logs.detail.ttft": "TTFT", "logs.detail.decodeTokPerSec": "デコード速度(推定)", "logs.detail.reason.ttft_missing": "このリクエストでは最初のトークンまでの時間が記録されていないため、測定できるデコード区間がありません。", - "logs.detail.reason.decode_window_too_short": "最初のトークン以降の区間が1秒未満で、デコード速度を推定するには短すぎます。", + "logs.detail.reason.decode_window_too_short": "計測された出力区間が1秒未満で、デコード速度を推定するには短すぎます。", "logs.detail.costTotal": "定価相当額", "logs.detail.totalTokens": "合計トークン", "logs.detail.matchedKey": "一致した価格キー", @@ -1212,9 +1238,12 @@ export const ja: Record = { "usage.col.reported": "報告", "usage.col.inputTokens": "入力トークン", "usage.col.outputTokens": "出力トークン", + "usage.col.tokPerSec": "出力 tok/s", "usage.col.cacheHits": "キャッシュヒット", "usage.col.cacheWrites": "キャッシュ書き込み", "usage.col.cacheHitRate": "ヒット率", + "usage.throughput.title": "エンドツーエンドの出力スループット: 出力トークンの合計 ÷ 経過時間の合計。トークンと時間の両方を報告した {samples} 件の試行が対象です。デコード前の待ち時間を含むため定常デコード速度より低く、トークンまたは時間が未報告のリクエストは対象外です。", + "usage.throughput.unmeasured": "この行には出力トークンと時間の両方を報告したリクエストがなく、スループットを平均できません。", "usage.cacheHitRate.partial": "入力トークン全体 {total} のうち {measured} を対象に平均しています。残りのリクエストではキャッシュの詳細が報告されていません。", "usage.cacheHitRate.unmeasured": "この行のリクエストはいずれもキャッシュの詳細を報告していないため、平均ヒット率はありません。", "usage.unavailable": "—", @@ -1916,6 +1945,16 @@ export const ja: Record = { "integrations.tab.cline": "Cline CLI", "integrations.tab.kilo": "Kilo", "integrations.tab.droid": "Factory Droid", + "integrations.droidReasoning.title": "推論レベルの既定値", + "integrations.droidReasoning.description": "リクエストに推論レベルが指定されていない場合にのみ既定値が使われます。Factory Droid を無効にすると、保存済みの既定値も削除されます。", + "integrations.droidReasoning.noDefault": "既定値なし", + "integrations.droidReasoning.noEfforts": "利用可能な推論強度の選択肢はありません", + "integrations.droidReasoning.noModels": "エクスポートされたモデルはありません。", + "integrations.droidReasoning.modelDefault": "{model} の推論レベルの既定値", + "integrations.droidReasoning.unsupportedTitle": "一部の保存済み既定値はサポートされなくなりました。削除した後、変更を保存 / 確認して適用してください。", + "integrations.droidReasoning.unsupportedEffort": "未サポートのレベル: {effort}", + "integrations.droidReasoning.clear": "既定値を削除", + "integrations.droidReasoning.saveReview": "変更を保存 / 確認", "integrations.aside.profilesTitle": "Asideのプロファイル", "integrations.aside.profilesHint": "選択したモデルを同期するプロファイルを選んでください。Asideで使用中のプロファイルは変わりません。", "integrations.aside.all": "すべてのプロファイルを同期", @@ -3581,6 +3620,15 @@ export const ja: Record = { "link.addChild": "子を追加", "link.sheetTitle": "子コンピューターを追加", "link.candidates": "SSH ホスト", + "link.setup.relationship": "Child は SSH 経由でこの Home の OpenCodex プロバイダーを使用します。", + "link.setup.requirements": "Home から Child への SSH 鍵認証、Child の OpenCodex 2.66.0 以降、両方のコンピューターの macOS または Linux が必要です。", + "link.setup.emptyHome": "別名はこの Home の ~/.ssh/config から取得します。Host エントリを追加して再検索するか、既存の別名を下に入力してください。", + "link.setup.emptyLocal": "別名はこのコンピューターの ~/.ssh/config から取得します。Host エントリを追加して再検索するか、既存の別名を下に入力してください。", + "link.setup.guide": "Remote Link 設定ガイド", + "link.discoveryFailed": "SSH ホストを読み込めませんでした", + "link.rescan": "ホストを再検索", + "link.aliasRequired": "接続テストを有効にするには、別名を選択または入力してください。", + "link.probeHelp": "上記の理由を確認してください。SSH 接続の問題の場合、このコンピューターの端末で を SSH の別名に置き換えて実行し、再試行してください。", "link.noCandidates": "SSH ホスト候補が見つかりません。", "link.alias": "SSH ホスト別名", "link.aliasPlaceholder": "SSH 設定の別名を入力", @@ -3619,6 +3667,8 @@ export const ja: Record = { "remoteLink.error.join_restart_failed": "リンクの準備ができました。このコンピューターで OpenCodex を再起動して、子としての接続を完了してください。", "remoteLink.error.join_port_failed": "リモートリンク要求を完了できませんでした。", "remoteLink.error.join_port_mismatch": "OpenCodex が設定されたポートで動作していないため、子として再起動できません。設定されたポートで OpenCodex を再起動してから、もう一度お試しください。", + "remoteLink.joinDenied.pairing_required": "子として接続するには、まずこのコンピューターをペアリングしてください。このコンピューターの loopback アドレスでダッシュボードを開き、管理者が作成したワンタイムペアリングコードを入力してください。", + "remoteLink.joinDenied.unavailable": "このダッシュボードセッションでは、子として接続できません。状態を更新し、このコンピューターのペアリングとランタイムの設定を確認してください。", "remoteLink.error.join_connect_failed": "リモートリンク要求を完了できませんでした。", "link.noChildren": "接続された子コンピューターはありません。", "remoteLink.status.connecting": "接続中", diff --git a/gui/src/i18n/ko.ts b/gui/src/i18n/ko.ts index 7c667aadc75..d32503633ed 100644 --- a/gui/src/i18n/ko.ts +++ b/gui/src/i18n/ko.ts @@ -238,6 +238,10 @@ export const ko: Record = { "theme.light": "라이트", "theme.dark": "다크", "theme.system": "시스템", + "zoom.label": "확대/축소", + "zoom.out": "축소", + "zoom.in": "확대", + "zoom.reset": "확대/축소를 100%로 재설정", "lang.label": "언어", "lang.nativeName": "한국어", "provider.name.commandCodeAuth": "Command Code - Auth", @@ -439,14 +443,25 @@ export const ko: Record = { "compactionRouting.triggersManual": "수동 /compact만", "compactionRouting.triggersBoth": "수동과 자동", "compactionRouting.triggersAuto": "자동만", + "compactionRouting.sources": "소스 범위", + "compactionRouting.sourcesAll": "모든 대화 모델", + "compactionRouting.sourcesSelected": "선택한 소스만", + "compactionRouting.sourcesProviders": "전체 제공자", + "compactionRouting.sourcesModels": "개별 모델", + "compactionRouting.sourcesSaved": "현재 카탈로그에 없는 저장된 선택자", + "compactionRouting.sourcesNone": "소스를 하나 이상 선택하거나 모든 대화 모델로 되돌리세요.", + "compactionRouting.sourcesHint": "압축 요청의 원본 모델이 선택한 모델 또는 provider/*와 일치할 때만 재정의를 적용합니다. 일치하지 않는 요청은 원래 모델을 사용합니다.", + "compactionRouting.sourcesGridTitle": "원본 모델이 다음 선택 항목과 일치하는 압축 요청을 다시 라우팅합니다:", "compactionRouting.currentModel": "대화 모델 사용", "compactionRouting.currentEffort": "요청의 추론 수준 유지", "compactionRouting.effortHint": "압축 엔드포인트가 지원하는 경우 추론 설정이 적용됩니다. 모델은 전체 대화를 수용할 수 있어야 합니다.", "compactionRouting.dataNotice": "적용 대상 압축 요청은 대화가 다른 프로바이더에서 실행 중이더라도 전체 대화를 선택한 모델의 프로바이더로 보내 요약합니다.", "compactionRouting.autoNotice": "자동 압축은 사용자가 요청하지 않아도 실행되므로, 긴 대화가 예고 없이 해당 프로바이더로 전송될 수 있습니다.", "compactionRouting.providerWarning": "이 설정을 사용하면 적용 대상 압축 요청마다 전체 대화 내용이 요약을 위해 {provider}로 전송됩니다.", - "compactionRouting.comboWarning": "이 설정을 사용하면 적용 대상 압축 요청마다 전체 대화 내용이 요약을 위해 콤보 {combo}로 전송됩니다. 콤보는 대상({providers})을 순서대로 시도해 먼저 응답한 하나를 사용하므로, 그중 어느 것이든 대화를 받을 수 있습니다.", + "compactionRouting.comboWarning": "이 설정을 사용하면 적용 대상 압축 요청마다 전체 대화 내용이 요약을 위해 콤보 {combo}로 전송됩니다. 콤보는 라우팅 전략에 따라 대상({providers}) 중 하나를 선택하지만, 재시도 가능한 실패가 발생하면 동일한 전체 대화 요청을 다른 대상에서 재시도할 수 있으므로, 대상 중 하나 이상이 대화를 받을 수 있습니다.", "compactionRouting.comboProvidersUnknown": "구성된 대상 프로바이더", + "compactionRouting.providerWarningScoped": "이 설정을 사용하면 원본 모델이 {sources}와 일치하는 압축 요청만 전체 대화 내용을 요약을 위해 {provider}로 전송합니다. 일치하지 않는 요청은 원래 모델을 사용합니다.", + "compactionRouting.comboWarningScoped": "이 설정을 사용하면 원본 모델이 {sources}와 일치하는 압축 요청만 전체 대화 내용을 요약을 위해 콤보 {combo}로 전송합니다. 일치하지 않는 요청은 원래 모델을 사용합니다. 콤보는 라우팅 전략에 따라 대상({providers}) 중 하나를 선택하지만, 재시도 가능한 실패가 발생하면 동일한 전체 대화 요청을 다른 대상에서 재시도할 수 있으므로, 대상 중 하나 이상이 대화를 받을 수 있습니다.", "compactionRouting.loadFailed": "압축 설정을 불러올 수 없습니다.", "compactionRouting.saved": "압축 설정을 저장했습니다.", "compactionRouting.saveFailed": "저장하지 못했습니다. 변경 사항은 유지됩니다. 다시 시도하세요.", @@ -880,6 +895,17 @@ export const ko: Record = { "models.selectedCount": "{n}개 선택", // subagents + "sub.forceTitle": "모든 서브에이전트 모델 고정", + "sub.forceModel": "Claude Code 서브에이전트 모델", + "sub.forceChoose": "공개된 모델 선택", + "sub.forceHelp": "플러그인 및 내장 에이전트(Explore/Plan 포함)에 적용되며, 호출별 모델 지정을 덮어씁니다. 포크와 model: inherit를 지정한 스킬은 메인 대화의 모델을 그대로 사용합니다. 메인 모델과 Haiku/small-fast 사이드카에는 영향을 주지 않습니다.", + "sub.forceScope": "기본값은 꺼짐입니다. 다음번에 라우팅을 통해 ocx claude를 실행할 때부터 적용되며, claude를 직접 실행할 때는 적용되지 않습니다. 셸에서 export한 환경 변수가 우선하며, settings.json의 env가 이를 다시 덮어쓸 수 있습니다. 상태 확인은 서버의 CLI와 사용자 설정만 대상으로 하며, 다른 셸이나 프로젝트 설정은 포함하지 않습니다.", + "sub.forceInvalid": "설정한 대상을 사용할 수 없어 실행 시 모델 덮어쓰기를 건너뜁니다. 공개된 모델을 선택하거나 이 기능을 꺼 주세요.", + "sub.forceOld": "Claude Code 버전이 2.1.257보다 낮아 FORCE는 무시되며, 강제 적용 없이 서브에이전트 기본 모델만 적용됩니다.", + "sub.forceUnknown": "Claude Code 버전을 확인할 수 없습니다. 모델 강제 적용에는 2.1.257 이상이 필요합니다.", + "sub.forceOverride": "settings.json이 이 설정을 덮어씁니다. 해당 파일의 env에 서브에이전트 모델 또는 FORCE 설정이 있습니다. OpenCodex는 이 파일을 수정하지 않습니다.", + "sub.forceSettingsUnknown": "settings.json을 확인할 수 없어 설정 덮어쓰기 여부를 알 수 없습니다.", + "sub.forceSaved": "저장했습니다. 다음번에 라우팅을 통해 ocx claude를 실행할 때 적용됩니다.", "sub.subtitle": "Codex의 {cmd} 는 우선순위 상위 5개 모델만 오버라이드로 노출합니다. 여기서 최대 5개를 선택하면 — 네이티브 gpt 또는 라우팅된 모델 — opencodex가 카탈로그 우선순위를 설정해 정확히 이들이 앞에 옵니다. 다른 모델도 정확한 이름으로 호출할 수 있으며, 이 설정은 표시 항목만 제어합니다.", "sub.featured": "추천", "sub.advanced": "고급", @@ -1050,7 +1076,7 @@ export const ko: Record = { "logs.detail.ttft": "TTFT", "logs.detail.decodeTokPerSec": "디코드 속도(추정)", "logs.detail.reason.ttft_missing": "이 요청은 첫 토큰까지 걸린 시간이 기록되지 않아서 측정할 디코드 구간이 없어요.", - "logs.detail.reason.decode_window_too_short": "첫 토큰 이후 구간이 1초도 안 돼서 디코드 속도를 추정하기엔 너무 짧아요.", + "logs.detail.reason.decode_window_too_short": "측정된 출력 구간이 1초도 안 돼서 디코드 속도를 추정하기엔 너무 짧아요.", "logs.detail.costTotal": "정가 환산치", "logs.detail.totalTokens": "전체 토큰", "logs.detail.matchedKey": "매칭된 가격 키", @@ -1309,9 +1335,12 @@ export const ko: Record = { "usage.col.reported": "측정됨", "usage.col.inputTokens": "입력 토큰", "usage.col.outputTokens": "출력 토큰", + "usage.col.tokPerSec": "출력 tok/s", "usage.col.cacheHits": "캐시 히트", "usage.col.cacheWrites": "캐시 쓰기", "usage.col.cacheHitRate": "히트율", + "usage.throughput.title": "엔드투엔드 출력 처리량: 출력 토큰 합계 ÷ 벽시계 시간 합계, 토큰과 시간을 모두 보고한 {samples}개 시도이 대상입니다. 디코딩 전 대기 시간을 포함하므로 안정적 디코딩 속도보다 낮으며, 토큰이나 시간을 보고하지 않은 요청은 제외됩니다.", + "usage.throughput.unmeasured": "이 행에는 출력 토큰과 시간을 모두 보고한 요청이 없어 처리량을 평균할 수 없습니다.", "usage.cacheHitRate.partial": "전체 입력 토큰 {total} 중 {measured}에 대해 계산한 평균입니다. 나머지 요청에는 캐시 세부 정보가 없습니다.", "usage.cacheHitRate.unmeasured": "이 행의 요청에는 모두 캐시 세부 정보가 없어 평균 히트율을 계산할 수 없습니다.", "usage.unavailable": "—", @@ -1527,6 +1556,16 @@ export const ko: Record = { "integrations.tab.cline": "Cline CLI", "integrations.tab.kilo": "Kilo", "integrations.tab.droid": "Factory Droid", + "integrations.droidReasoning.title": "기본 추론 강도", + "integrations.droidReasoning.description": "요청에 추론 강도가 지정되지 않은 경우에만 기본값을 사용합니다. Factory Droid를 비활성화하면 저장된 기본값도 제거됩니다.", + "integrations.droidReasoning.noDefault": "기본값 없음", + "integrations.droidReasoning.noEfforts": "사용 가능한 추론 강도 없음", + "integrations.droidReasoning.noModels": "내보낸 모델이 없습니다.", + "integrations.droidReasoning.modelDefault": "{model}의 기본 추론 강도", + "integrations.droidReasoning.unsupportedTitle": "저장된 기본값 중 일부가 더 이상 지원되지 않습니다. 지운 다음 변경 사항을 저장 / 검토해야 적용됩니다.", + "integrations.droidReasoning.unsupportedEffort": "지원하지 않는 강도: {effort}", + "integrations.droidReasoning.clear": "기본값 지우기", + "integrations.droidReasoning.saveReview": "변경 사항 저장 / 검토", "integrations.aside.profilesTitle": "Aside 프로필", "integrations.aside.profilesHint": "선택한 모델을 동기화할 프로필을 고르세요. Aside에서 사용 중인 프로필은 바뀌지 않습니다.", "integrations.aside.all": "모든 프로필 동기화", @@ -3581,6 +3620,15 @@ export const ko: Record = { "link.addChild": "자식 추가", "link.sheetTitle": "자식 컴퓨터 추가", "link.candidates": "SSH 호스트", + "link.setup.relationship": "Child는 SSH를 통해 이 Home의 OpenCodex 공급자를 사용합니다.", + "link.setup.requirements": "Home에서 Child로의 SSH 키 인증, Child의 OpenCodex 2.66.0 이상, 두 컴퓨터의 macOS 또는 Linux가 필요합니다.", + "link.setup.emptyHome": "별칭은 이 Home의 ~/.ssh/config에서 가져옵니다. Host 항목을 추가하고 다시 검색하거나 아래에 기존 별칭을 입력하세요.", + "link.setup.emptyLocal": "별칭은 이 컴퓨터의 ~/.ssh/config에서 가져옵니다. Host 항목을 추가하고 다시 검색하거나 아래에 기존 별칭을 입력하세요.", + "link.setup.guide": "Remote Link 설정 안내", + "link.discoveryFailed": "SSH 호스트를 불러오지 못했습니다", + "link.rescan": "호스트 다시 검색", + "link.aliasRequired": "연결 테스트를 활성화하려면 별칭을 선택하거나 입력하세요.", + "link.probeHelp": "위의 원인을 확인하세요. SSH 접근 문제라면 이 컴퓨터의 터미널에서 를 SSH 별칭으로 바꿔 실행한 뒤 다시 시도하세요.", "link.noCandidates": "SSH 호스트 후보를 찾지 못했습니다.", "link.alias": "SSH 호스트 별칭", "link.aliasPlaceholder": "SSH 설정의 별칭을 입력하세요", @@ -3619,6 +3667,8 @@ export const ko: Record = { "remoteLink.error.join_restart_failed": "링크가 준비되었습니다. 이 컴퓨터에서 OpenCodex를 다시 시작해 자식 연결을 완료하세요.", "remoteLink.error.join_port_failed": "원격 연결 요청을 완료하지 못했습니다.", "remoteLink.error.join_port_mismatch": "OpenCodex가 설정된 포트에서 실행되고 있지 않아 자식으로 다시 시작할 수 없습니다. 설정된 포트에서 OpenCodex를 다시 시작한 뒤 다시 시도하세요.", + "remoteLink.joinDenied.pairing_required": "자식으로 연결하려면 먼저 이 컴퓨터를 페어링하세요. 루프백 주소로 이 컴퓨터의 대시보드를 열고 운영자가 생성한 일회용 페어링 코드를 입력하세요.", + "remoteLink.joinDenied.unavailable": "이 대시보드 세션에서는 자식으로 연결할 수 없습니다. 상태를 새로고침하고 이 컴퓨터의 페어링 및 런타임 설정을 확인하세요.", "remoteLink.error.join_connect_failed": "원격 연결 요청을 완료하지 못했습니다.", "link.noChildren": "연결된 자식 컴퓨터가 없습니다.", "remoteLink.status.connecting": "연결 중", diff --git a/gui/src/i18n/lab-translations.ts b/gui/src/i18n/lab-translations.ts index 59d1639e80a..8b4405f74fe 100644 --- a/gui/src/i18n/lab-translations.ts +++ b/gui/src/i18n/lab-translations.ts @@ -1,6 +1,6 @@ import type { TKey } from "./en"; -export type LabLocale = "en" | "de" | "fr" | "ko" | "zh" | "zh-TW" | "ru" | "ja" | "tr" | "vi"; +export type LabLocale = "en" | "de" | "fr" | "ko" | "zh" | "zh-TW" | "ru" | "ja" | "tr" | "vi" | "pt"; export type LabCatalogKey = Exclude, `lab.production.${string}`>; export type LabSupplementKey = | "subjectKindUnknown" @@ -514,6 +514,56 @@ const vi: Record = { "lab.layer.task_effectiveness": "Hiệu quả tác vụ", }; +const pt: Record = { + "lab.title": "Laboratório de Compatibilidade", + "lab.subtitle": "Matriz de vereditos de compatibilidade somente leitura, gerada a partir das evidências da projeção do laboratório.", + "lab.loadFailed": "Não foi possível carregar os dados do laboratório de compatibilidade", + "lab.projectionUnavailable": "A projeção do laboratório não está disponível. Execute primeiro os testes de conformidade ou as sondagens ao vivo.", + "lab.projectionIncompatible": "O esquema da projeção do laboratório é incompatível. Reconstrua a projeção.", + "lab.statusTitle": "Status da projeção", + "lab.matrixTitle": "Matriz de compatibilidade", + "lab.verdictsTitle": "Registros de veredito", + "lab.filter.layer": "Camada de evidência", + "lab.filter.verdict": "Veredito", + "lab.filter.subject": "ID do sujeito", + "lab.filter.all": "Todos", + "lab.col.subject": "Sujeito", + "lab.col.layer": "Camada", + "lab.col.suite": "Suíte", + "lab.col.verdict": "Veredito", + "lab.col.asOf": "Data de referência", + "lab.col.protocol": "Conformidade de protocolo", + "lab.col.live": "Compatibilidade da rota ao vivo", + "lab.col.task": "Eficácia nas tarefas", + "lab.empty": "Ainda não há vereditos de compatibilidade na projeção.", + "lab.subjectKind": "Tipo", + "lab.observationCount": "Observações", + "lab.eventCount": "Eventos", + "lab.verdictCount": "Vereditos", + "lab.subjectCount": "Sujeitos", + "lab.builtAt": "Gerada em", + "lab.loading": "Carregando evidências de compatibilidade…", + "lab.loadMore": "Carregar mais", + "lab.detailTitle": "Detalhes do veredito", + "lab.detailClose": "Fechar", + "lab.detailSubject": "Sujeito", + "lab.detailObservations": "Observações", + "lab.detailEvents": "Eventos de evidência", + "lab.detailArtifacts": "Metadados dos artefatos", + "lab.detailLoadFailed": "Não foi possível carregar os detalhes do veredito", + "lab.refresh": "Atualizar", + "lab.verdict.UNKNOWN": "Desconhecido", + "lab.verdict.CLAIMED": "Declarado", + "lab.verdict.PROBED": "Sondado", + "lab.verdict.VERIFIED": "Verificado", + "lab.verdict.DEGRADED": "Degradado", + "lab.verdict.BLOCKED": "Bloqueado", + "lab.verdict.UNSUPPORTED": "Não suportado", + "lab.layer.protocol_conformance": "Conformidade de protocolo", + "lab.layer.live_route_compatibility": "Compatibilidade da rota ao vivo", + "lab.layer.task_effectiveness": "Eficácia nas tarefas", +}; + export const LAB_CATALOG_OVERRIDES: Record> = { en, de, @@ -525,6 +575,7 @@ export const LAB_CATALOG_OVERRIDES: Record> = { @@ -648,6 +699,18 @@ const supplements: Record> = { "community.activeRecords": "Bản ghi đang hoạt động", "community.revokedRecords": "Bản ghi đã thu hồi", }, + pt: { + subjectKindUnknown: "Desconhecido", + "artifact.present": "Presente", + "artifact.corrupt": "Corrompido", + "artifact.purged_unavailable": "Removido / indisponível", + selectVerdict: "Ver veredito de {subject}", + "community.title": "Evidências da comunidade", + "community.notLocalVerdict": "Contexto somente leitura não confiável. Não incluído neste veredito local.", + "community.bundles": "Pacotes", + "community.activeRecords": "Registros ativos", + "community.revokedRecords": "Registros revogados", + }, }; export function labSupplement( diff --git a/gui/src/i18n/log-guard-labels.ts b/gui/src/i18n/log-guard-labels.ts index 7c0d329fb9e..c32a5662efb 100644 --- a/gui/src/i18n/log-guard-labels.ts +++ b/gui/src/i18n/log-guard-labels.ts @@ -298,6 +298,33 @@ const LABELS: Record> = { "error.database_error": "Không thể cập nhật cơ sở dữ liệu nhật ký Codex.", "error.config_write_failed": "Cơ sở dữ liệu đã thay đổi nhưng OpenCodex không thể lưu cài đặt bảo vệ. Vui lòng sửa lỗi lưu trữ cấu hình, sau đó chạy Sửa chữa.", }, + pt: { + compact: 'Compactar', + compactComplete: "Compactação concluída (lógico / recuperado em disco)", + pagesUnit: "páginas", + compactPartial: "Compactação parcial (lógico / recuperado em disco)", + confirmCompact: 'Confirmar compactação', + cancel: 'Cancelar', + inspectionOnly: 'Somente inspeção', + externalSqliteHome: 'Armazenamento SQLite externo', + inspectionUnavailable: "A inspeção do log de diagnóstico não está disponível.", + metricsSkippedLarge: "Métricas de linhas ignoradas: o banco de dados excede {threshold} e a varredura travaria o proxy.", + protection: "Proteção", + compat: "Compatibilidade", + quiet: "Silencioso", + disable: "Desativar proteção", + repair: "Reparar proteção", + applying: "Aplicando proteção…", + "error.generic": "Não foi possível alterar a proteção de logs do Codex.", + "error.codex_running": "Feche o Codex antes de alterar a proteção de logs.", + "error.process_enumeration_failed": "Não foi possível verificar se o Codex está parado. A proteção não foi alterada.", + "error.busy": "O banco de dados de logs do Codex está ocupado. Feche o Codex e tente novamente.", + "error.unsupported_schema": "Este esquema de logs do Codex não é suportado para proteção.", + "error.trigger_collision": "Um nome de trigger reservado do Log Guard já está em uso. A proteção não foi alterada.", + "error.unsafe_path": "O caminho do banco de dados de logs do Codex falhou na verificação de segurança.", + "error.database_error": "Não foi possível atualizar o banco de dados de logs do Codex.", + "error.config_write_failed": "O banco de dados foi alterado, mas o OpenCodex não conseguiu salvar a configuração de proteção. Corrija o armazenamento de configuração e execute Reparar.", + }, }; export function logGuardLabel(locale: Locale, key: LogGuardLabelKey): string { diff --git a/gui/src/i18n/log-guard-operation-labels.ts b/gui/src/i18n/log-guard-operation-labels.ts index 8eded1eeddf..173fee49002 100644 --- a/gui/src/i18n/log-guard-operation-labels.ts +++ b/gui/src/i18n/log-guard-operation-labels.ts @@ -133,6 +133,18 @@ const LABELS: Record> = { "error.auto_vacuum_not_incremental": "Cơ sở dữ liệu nhật ký Codex này không được định cấu hình cho tính năng incremental vacuum, do đó không thể thu hồi dung lượng nếu không xây dựng lại hoàn toàn.", "error.integrity_check_failed": "Cơ sở dữ liệu nhật ký Codex không vượt qua được kiểm tra tính toàn vẹn. Không có dung lượng nào được thu hồi.", }, + pt: { + applying: "Aplicando alteração do Log Guard…", + "error.generic": "Não foi possível atualizar o armazenamento de logs do Codex.", + "error.codex_running": "Feche o Codex antes de alterar o armazenamento de logs do Codex.", + "error.process_enumeration_failed": "Não foi possível verificar se o Codex está parado. A operação do Log Guard não foi iniciada.", + "error.busy": "O banco de dados de logs do Codex está ocupado. Feche o Codex e tente novamente.", + "error.unsupported_schema": "Este esquema de logs do Codex não é suportado para esta operação.", + "error.unsafe_path": "O caminho do banco de dados de logs do Codex falhou na verificação de segurança.", + "error.database_error": "Não foi possível atualizar o banco de dados de logs do Codex.", + "error.auto_vacuum_not_incremental": "Este banco de dados de logs do Codex não está configurado para incremental vacuum; por isso, não é possível recuperar espaço sem uma reconstrução completa.", + "error.integrity_check_failed": "O banco de dados de logs do Codex falhou na verificação de integridade. Nenhum espaço foi recuperado.", + }, }; export function logGuardOperationLabel(locale: Locale, key: LogGuardOperationLabelKey): string { diff --git a/gui/src/i18n/log-guard-state-labels.ts b/gui/src/i18n/log-guard-state-labels.ts index 32296ee0eb5..71c789cbb1c 100644 --- a/gui/src/i18n/log-guard-state-labels.ts +++ b/gui/src/i18n/log-guard-state-labels.ts @@ -61,6 +61,11 @@ const LABELS: Record = { protection: { off: "Tắt", active: "Đang hoạt động", drifted: "Cần sửa chữa", unsupported: "Không được hỗ trợ", unknown: "Không xác định" }, mode: { off: "Tắt", compat: "Tương thích", quiet: "Yên tĩnh", collision: "Không xác định" }, }, + pt: { + schema: { compatible: "Compatível", missing: "Banco de dados não encontrado", unreadable: "Banco de dados indisponível", unsupported: "Não suportado" }, + protection: { off: "Desativada", active: "Ativa", drifted: "Requer reparo", unsupported: "Não suportada", unknown: "Desconhecida" }, + mode: { off: "Desativado", compat: "Compatibilidade", quiet: "Silencioso", collision: "Desconhecido" }, + }, }; export function logGuardSchemaStateLabel(locale: Locale, state: LogGuardSchemaState): string { diff --git a/gui/src/i18n/native-main-copy.ts b/gui/src/i18n/native-main-copy.ts index 71091e37468..09add82754b 100644 --- a/gui/src/i18n/native-main-copy.ts +++ b/gui/src/i18n/native-main-copy.ts @@ -3,7 +3,7 @@ import { NATIVE_MAIN_TRANSLATIONS, type NativeMainTKey } from "./native-main-tra export type NativeMainTFn = (key: NativeMainTKey, vars?: Record) => string; -/** Closed, nine-locale namespace like the adjacent Log Guard label modules. +/** Closed locale namespace like the adjacent Log Guard label modules. * Keep these keys out of the global catalog contract used by existing screens. */ export function nativeMainTranslator(locale: Locale): NativeMainTFn { diff --git a/gui/src/i18n/native-main-translations.ts b/gui/src/i18n/native-main-translations.ts index a1f53768ae2..59ae5f2d294 100644 --- a/gui/src/i18n/native-main-translations.ts +++ b/gui/src/i18n/native-main-translations.ts @@ -382,4 +382,42 @@ export const NATIVE_MAIN_TRANSLATIONS = { "nativeMain.confirm": "Xác nhận thay đổi đăng nhập gốc", "nativeMain.unavailable": "Không thể thay đổi hồ sơ. Hãy kiểm tra mã chẩn đoán trước khi tiếp tục." }, -} satisfies Record<"en" | "de" | "fr" | "ko" | "zh" | "zh-TW" | "ru" | "ja" | "tr" | "vi", Record>; + "pt": { + "nativeMain.title": "Login principal do Codex nativo", + "nativeMain.manage": "Gerenciar login principal", + "nativeMain.close": "Fechar o gerenciador de login principal", + "nativeMain.separate": "Estes controles alteram o login do Codex nativo, não a conta selecionada para a próxima requisição do Pool.", + "nativeMain.home": "CODEX_HOME efetivo", + "nativeMain.current": "Perfil ativo registrado", + "nativeMain.unregistered": "Não registrado", + "nativeMain.physicalHint": "Este diretório home pertence ao servidor do OpenCodex, que pode ser outro computador. O backend verifica o login físico antes de qualquer troca.", + "nativeMain.busyOther": "Outra operação de login nativo está em andamento. Conclua ou cancele-a primeiro.", + "nativeMain.working": "Processando…", + "nativeMain.changed": "O diretório home, o perfil ativo ou o estado de recuperação mudou. Revise o estado atualizado e confirme novamente.", + "nativeMain.error": "Não foi possível confirmar a operação. Leia o estado atualizado antes de tentar de novo; uma resposta perdida não prova que houve reversão. Use o comando de diagnóstico no servidor abaixo.", + "nativeMain.doctorHint": "Execute {cmd} no servidor para ver os detalhes.", + "nativeMain.saved": "Login atual salvo como perfil. O login físico não foi alterado.", + "nativeMain.restart": "Troca de login concluída. Reinicie o Codex nativo usando o CODEX_HOME exibido antes de continuar.", + "nativeMain.done": "Operação concluída. O servidor não solicitou a reinicialização do Codex nativo.", + "nativeMain.refreshFailed": "Não foi possível atualizar todas as informações da conta. Uma alteração já concluída não foi desfeita. Atualize antes de outra operação.", + "nativeMain.refresh": "Atualizar status", + "nativeMain.unsupported": "Este armazenamento de credenciais não é suportado. O gerenciamento de perfis nativos exige o armazenamento de credenciais em arquivo.", + "nativeMain.recoveryHint": "Uma transação interrompida precisa de recuperação. Recuperar conclui a reconciliação; restaurar reverte a transação pendente. Nenhuma das ações é executada até você confirmar.", + "nativeMain.recover": "Recuperar alteração interrompida", + "nativeMain.rollback": "Restaurar transação pendente", + "nativeMain.empty": "Nenhum perfil nativo registrado ainda. Salve primeiro o login atual.", + "nativeMain.active": "Ativo", + "nativeMain.switchTo": "Trocar o login nativo para {label}", + "nativeMain.switch": "Trocar", + "nativeMain.restore": "Voltar ao perfil exibido anteriormente: {label}", + "nativeMain.restoreHint": "Este atalho seleciona o perfil exibido antes da sua última troca, não um histórico de transações verificado pelo servidor. Ele é mantido apenas nesta página.", + "nativeMain.label": "Rótulo do perfil", + "nativeMain.save": "Salvar atual como perfil", + "nativeMain.saveHint": "Para um login ativo já registrado, salvar atualiza o rótulo. Não adiciona um novo login.", + "nativeMain.confirmHint": "Isto altera apenas o login nativo no diretório home abaixo. Feche primeiro o Codex nativo naquele computador. Este painel não edita estratégia do Pool, membros, chaves de provedor nem dados de tarefas/histórico.", + "nativeMain.stopped": "Parei o Codex nativo que usa este CODEX_HOME e entendo que pode ser necessário reiniciá-lo.", + "nativeMain.cancel": "Cancelar", + "nativeMain.confirm": "Confirmar alteração do login nativo", + "nativeMain.unavailable": "As alterações de perfil estão indisponíveis. Verifique o código de diagnóstico antes de continuar." +}, +} satisfies Record<"en" | "de" | "fr" | "ko" | "zh" | "zh-TW" | "ru" | "ja" | "tr" | "vi" | "pt", Record>; diff --git a/gui/src/i18n/pt.ts b/gui/src/i18n/pt.ts new file mode 100644 index 00000000000..8916a78ba48 --- /dev/null +++ b/gui/src/i18n/pt.ts @@ -0,0 +1,3801 @@ +// Brazilian Portuguese — generated from en.ts. Must match TKey set (compile-checked). +import type { TKey } from "./en"; + +/** + * Brazilian Portuguese (pt-BR) i18n catalog. Must match the `TKey` set (compile-checked). + * Technical terms and model identifiers intentionally remain English. + */ +export const pt: Record = { + "compactionRouting.sources": "Origens", + "compactionRouting.sourcesAll": "Todos os modelos de conversa", + "compactionRouting.sourcesSelected": "Somente origens selecionadas", + "compactionRouting.sourcesProviders": "Provedores inteiros", + "compactionRouting.sourcesModels": "Modelos individuais", + "compactionRouting.sourcesSaved": "Seletores salvos ausentes no catálogo atual", + "compactionRouting.sourcesNone": "Selecione pelo menos uma origem ou volte para todos os modelos de conversa.", + "compactionRouting.sourcesHint": "Aplique a substituição somente quando o modelo de origem da requisição de compactação corresponder a um modelo selecionado ou a provedor/*. As demais requisições continuam usando seu próprio modelo.", + "compactionRouting.sourcesGridTitle": "Redirecionar requisições de compactação cujo modelo de origem corresponda a:", + "compactionRouting.providerWarningScoped": "Com esta configuração, somente requisições de compactação cujo modelo de origem corresponda a {sources} enviam todo o conteúdo da conversa para {provider} resumir; as demais continuam usando seu próprio modelo.", + "compactionRouting.comboWarningScoped": "Com esta configuração, somente requisições de compactação cujo modelo de origem corresponda a {sources} enviam todo o conteúdo da conversa ao combo {combo} para resumo; as demais continuam usando seu próprio modelo. O combo escolhe um de seus destinos ({providers}) pela estratégia de roteamento, mas após uma falha recuperável pode repetir a mesma requisição com a conversa completa em outro destino. Assim, um ou mais provedores de destino podem receber a conversa.", + "usage.col.tokPerSec": "Saída tok/s", + "usage.throughput.title": "Taxa de saída de ponta a ponta: soma dos tokens de saída dividida pela soma dos tempos totais de {samples} tentativas medidas. Inclui a espera antes da decodificação, não apenas sua velocidade estável; requisições sem tokens medidos ou sem duração medida são excluídas.", + "usage.throughput.unmeasured": "Nenhuma requisição desta linha informou tokens de saída e duração válidos; não há taxa para calcular.", + "integrations.droidReasoning.title": "Esforço de raciocínio padrão", + "integrations.droidReasoning.description": "O padrão só é usado quando a requisição omite o esforço de raciocínio. Desative o Factory Droid para remover estes padrões salvos.", + "integrations.droidReasoning.noDefault": "Sem padrão", + "integrations.droidReasoning.noEfforts": "Nenhuma opção de esforço de raciocínio disponível", + "integrations.droidReasoning.noModels": "Nenhum modelo exportado está disponível.", + "integrations.droidReasoning.modelDefault": "Esforço de raciocínio padrão para {model}", + "integrations.droidReasoning.unsupportedTitle": "Alguns padrões salvos não são mais compatíveis. Limpe-os e salve / revise as alterações para aplicar.", + "integrations.droidReasoning.unsupportedEffort": "Esforço não compatível: {effort}", + "integrations.droidReasoning.clear": "Limpar padrão", + "integrations.droidReasoning.saveReview": "Salvar / revisar alterações", + + "nav.claude": "Claude", + "claude.tabAccount": "Conta", + "claude.tabSettings": "Configurações", + "claude.pageSub": "Contas e roteamento do OpenCodex para o Claude Code e o Claude Desktop.", + "claude.addAnthropic": "Adicionar Anthropic", + "claude.changeOnCode": "Altere na aba {tab}", + "claude.interceptRunning": "Em execução", + "claude.interceptStopped": "Parada", + "claude.routingLoadFail": "Não foi possível carregar o status do roteamento do Claude.", + "claude.interceptLoadFail": "Não foi possível carregar o status da interceptação.", + "claude.accountEmpty": "Adicione o provedor Anthropic e entre com sua conta Claude. Suas contas, cota e configurações aparecerão aqui.", + "claude.accountEmptyTitle": "A Anthropic ainda não foi configurada", + "claude.interceptStatus": "Interceptação", + "claude.interceptDesc": "Encaminha pelo proxy local o tráfego do Claude Code/Desktop destinado aos serviços da Anthropic.", + "claude.interceptPort": "Porta de interceptação", + "claude.interceptPortDesc": "Porta local do proxy de interceptação, separada da porta do proxy do OpenCodex.", + "claude.stateOn": "Ativada", + "claude.stateOff": "Desativada", + "claude.settingsHint": "As configurações de compatibilidade, instruções do agente e contexto podem ser alteradas na aba Code.", + "pws.anthropicAccountThresholdHint": "Substitui o padrão do pool do Claude. 0 desativa a troca por uso apenas para esta conta; a pausa e a recuperação de limite de taxa continuam valendo.", + "kiroLogin.title": "Entrar no Kiro", + "kiroLogin.chooseMethod": "Escolha um método de login", + "kiroLogin.cli": "Kiro CLI", + "kiroLogin.builderId": "Builder ID", + "kiroLogin.google": "Google", + "kiroLogin.github": "GitHub", + "kiroLogin.addDescription": "Adicione uma conta Kiro com login por dispositivo ou importe uma sessão do Kiro CLI.", + "kiroLogin.loginDescription": "Entre com um dispositivo ou importe sua sessão do Kiro CLI.", + "kiroLogin.starting": "Iniciando login por dispositivo…", + "kiroLogin.pending": "Digite este código na página de verificação e aguarde aqui.", + "kiroLogin.done": "Conta Kiro adicionada.", + "kiroLogin.expired": "O login por dispositivo expirou. Tente novamente.", + "kiroLogin.ended": "Login encerrado. Verifique a lista de contas.", + "kiroLogin.failed": "Falha no login do Kiro.", + "kiroLogin.startFailed": "Não foi possível iniciar o login do Kiro. Tente novamente.", + "kiroLogin.tryAgain": "Tentar novamente", + "kiroLogin.copyCode": "Copiar código", + "kiroLogin.copied": "Código copiado", + "kiroLogin.copyUnavailable": "Cópia indisponível", + "kiroLogin.openVerification": "Abrir página de verificação", + "kiroLogin.unexpectedHost": "Host de verificação inesperado. Confira o endereço antes de copiá-lo.", + "kiroLogin.duplicate": "Este perfil do Kiro já está na lista de contas.", + "kiroLogin.manualReview": "Esta conta pode precisar de revisão manual.", + "kiroSelection.suspended": "Não selecionada automaticamente: suspensa", + "kiroSelection.quotaExhausted": "Não selecionada automaticamente: cota esgotada", + "kiroSelection.cooldown": "Não selecionada automaticamente: em espera", + "kiroSelection.generic": "Não selecionada automaticamente", + "tray.updated": "Atualizado {time} · a cada 60 s", + "tray.today": "Hoje", + "tray.input": "Entrada", + "tray.output": "Saída", + "tray.cost": "Custo · est.", + "tray.cached": "{percent} em cache", + "usage.incomplete": "Alguns registros de uso não puderam ser incluídos. Contagens, datas e rankings refletem apenas os registros legíveis.", + "models.pickerOrder.usageIncomplete": "Não é possível salvar a ordem por mais usados porque o histórico de uso está incompleto. Escolha outra ordem ou repare o histórico primeiro.", + "api.attribution.noRecordedUse": "Sem uso nos registros legíveis", + "audio.dictation": "Ditado", + "audio.liveVoice": "Voz ao vivo", + "audio.configured": "Configurado, não verificado", + "audio.notConfigured": "Não configurado", + "audio.unknown": "Metadados de áudio indisponíveis", + "audio.key": "Chave de dados do OpenCodex", + "audio.file": "Arquivo de áudio (máx. 25 MB)", + "audio.transcribe": "Transcrever", + "audio.transcribing": "Transcrevendo...", + "audio.transcript": "Transcrição", + "audio.emptyTranscript": "Nenhuma fala detectada", + "audio.examples": "Exemplos de API", + "audio.streaming": "Ditado em streaming", + "audio.connect": "Verificar conexão", + "audio.disconnect": "Desconectar", + "audio.events": "Eventos da sessão", + "audio.state.idle": "Não verificado", + "audio.state.connecting": "Conectando...", + "audio.state.connected": "Sessão pronta", + "audio.state.disconnected": "Desconectado", + "audio.state.failed": "Falha na conexão", + "audio.error.auth": "Chave rejeitada. Verifique sua chave de dados do OpenCodex.", + "audio.error.unavailable": "Upstream indisponível. Verifique a conta do provedor.", + "audio.error.rateLimit": "Limite de taxa atingido. Tente novamente mais tarde.", + "audio.error.invalid": "Requisição rejeitada. Verifique o arquivo e o provedor.", + "audio.error.size": "Escolha um arquivo de áudio não vazio de até 25 MB.", + "audio.error.network": "Falha na conexão. Verifique o endereço do proxy.", + "audio.error.timeout": "A requisição expirou. Tente novamente.", + "audio.error.protocol": "Resposta de áudio inesperada. Verifique a compatibilidade do provedor.", + "models.pickerOrder.label": "Ordem do seletor", + "models.pickerOrder.default": "Padrão", + "models.pickerOrder.alphabetical": "A–Z por modelo", + "models.pickerOrder.provider": "Agrupar por provedor", + "models.pickerOrder.mostUsed": "Instantâneo dos mais usados", + "models.pickerOrder.custom": "Ordem personalizada", + "models.pickerOrder.apply": "Aplicar ordem", + "models.pickerOrder.applying": "Aplicando…", + "models.pickerOrder.saved": "Ordem do seletor salva. Reabra os clientes que ainda mostram o catálogo antigo.", + "models.pickerOrder.pending": "Ordem salva; a atualização do catálogo está pendente.", + "models.pickerOrder.usageFailed": "Não foi possível carregar o uso dos modelos.", + "models.pickerOrder.loadFailed": "Não foi possível carregar as configurações do seletor.", + "models.pickerOrder.retry": "Tentar novamente", + "models.pickerOrder.hint": "Salva a ordem roteada para a descoberta do Codex e do Claude. As faixas em destaque/nativas permanecem no lugar; Mais usados é um instantâneo. As opções nativas anunciadas podem mudar.", + "models.fastRows.title": "Mostrar linhas de modelos Fast", + "models.fastRows.desc": "Adiciona seletores “Model Fast” elegíveis aos seletores de modelos de clientes externos. Isso não altera as opções de esforço de raciocínio do modelo base. Ao salvar, as integrações conectadas são atualizadas quando possível; caso contrário, atualize a integração ou o catálogo do cliente.", + "models.fastRows.refreshHint": "Salvo. A atualização do catálogo está pendente.", + "models.fastRows.enabled": "Linhas de modelos Fast ativadas.", + "models.fastRows.disabled": "Linhas de modelos Fast desativadas.", + "models.fastRows.loadFailed": "Não foi possível carregar a configuração das linhas Fast.", + "models.fastRows.updateFailed": "Falha ao atualizar a configuração das linhas Fast.", + "codexAuth.quotaAutoRefreshAllHint": "Controla juntas as janelas compatíveis de 5 horas e semanal de todas as contas atuais. No modo Pool, uma pequena requisição é enviada após cada redefinição e consome cota.", + "codexAuth.quotaAutoRefreshMixed": "Algumas janelas estão ativadas.", + "codexAuth.quotaAutoRefreshEmpty": "Nenhuma janela de cota compatível. Atualize as cotas das contas para verificar novamente.", + "codexAuth.quotaAutoRefreshLoadFailed": "Não foi possível carregar as configurações de ativação. Tente novamente para verificar o estado delas.", + "codexAuth.quotaAutoRefreshPartial": "Algumas configurações não puderam ser salvas. Tente novamente para concluir a mesma alteração.", + "nav.dashboard": "Painel", + "uptime.day": "d", + "uptime.hour": "h", + "uptime.minute": "m", + "uptime.second": "s", + "nav.startup": "Inicialização", + "nav.providers": "Provedores", + "nav.models": "Modelos", + "nav.combos": "Combos", + "nav.subagents": "Subagentes", + "nav.logs": "Logs e depuração", + "nav.usage": "Uso", + "common.github": "GitHub", + "sidebar.star": "Dar estrela no GitHub", + "sidebar.starred": "Estrela dada no GitHub", + "sidebar.starUnauthenticated": "Abra o GitHub para dar a estrela (a CLI gh não está autenticada)", + "sidebar.starFailed": "Não foi possível dar a estrela pelo gh. Abrindo o GitHub.", + "onboarding.star.title": "Gostando do OpenCodex?", + "onboarding.star.body": "O OpenCodex é gratuito e de código aberto. Uma estrela no GitHub ajuda outros desenvolvedores a encontrá-lo e mantém o projeto vivo.", + "onboarding.star.cta": "Dar estrela no GitHub", + "onboarding.star.ghHint": "Dá a estrela no repositório com a CLI do GitHub (gh) autenticada. Nada é marcado até você clicar.", + "onboarding.star.browserHint": "Abre o repositório em github.com no navegador.", + "onboarding.star.openPage": "Abrir a página do GitHub", + "onboarding.star.later": "Talvez depois", + "onboarding.star.failed": "Não foi possível dar a estrela pela CLI do GitHub (gh). Você pode dá-la na página do GitHub.", + "onboarding.star.starring": "Dando estrela…", + "onboarding.star.thanks": "Obrigado pela estrela!", + "onboarding.star.thanksBody": "Ajuda muito. O link do GitHub na barra lateral leva você de volta ao repositório a qualquer momento.", + "onboarding.star.done": "Concluído", + "sidebar.updateAvailable": "Atualização disponível: {version}", + "sidebar.checkUpdate": "Verificar atualizações", + "sidebar.desktopUpdate": "Abrir atualizações do desktop", + "common.save": "Salvar", + "common.saving": "Salvando…", + "common.cancel": "Cancelar", + "common.discard": "Descartar", + "common.delete": "Excluir", + "common.close": "Fechar", + "common.ok": "OK", + "common.remove": "Remover", + "common.loading": "Carregando…", + "common.retry": "Tentar novamente", + "auth.adminTokenTitle": "Token de administrador do OpenCodex (OPENCODEX_ADMIN_AUTH_TOKEN)", + "auth.adminAccountLabel": "Conta", + "auth.adminTokenFieldLabel": "Token de administrador", + "auth.adminTokenRejected": "Esse token de administrador foi rejeitado. Confira-o e tente novamente.", + "auth.adminTokenUnavailable": "Não foi possível verificar o token de administrador. Tente novamente.", + "auth.adminTokenHelp": "Este é o token de administrador de gerenciamento do OpenCodex, não uma chave de API de provedor. O proxy o grava em ~/.opencodex/admin-api-token (ou $OPENCODEX_HOME/admin-api-token) na primeira inicialização, e OPENCODEX_ADMIN_AUTH_TOKEN o substitui.", + "auth.adminTokenDocsLink": "Como encontrá-lo", + "app.logoAria": "logotipo do opencodex", + "app.claudeOn": "Claude ON", + "app.claudeOff": "Claude OFF", + "theme.label": "Tema", + "theme.light": "Claro", + "theme.dark": "Escuro", + "theme.system": "Sistema", + "zoom.label": "Zoom", + "zoom.out": "Diminuir zoom", + "zoom.in": "Aumentar zoom", + "zoom.reset": "Redefinir zoom para 100%", + "lang.label": "Idioma", + "lang.nativeName": "Português", + "provider.name.commandCodeAuth": "Command Code - Auth", + "provider.name.commandCodeApi": "Command Code - API", + "provider.name.orcaRouterApi": "OrcaRouter - API", + "provider.name.orcaRouterAuth": "OrcaRouter - Auth", + "provider.name.volcengine": "Volcengine Ark", + "provider.name.volcengineCodingPlan": "Volcengine Ark Coding Plan", + "provider.name.volcengineAgentPlan": "Volcengine Ark Agent Plan", + "errorBoundary.title": "Falha ao carregar a página", + "errorBoundary.message": "Esta seção encontrou um erro de renderização. Recarregue-a para tentar novamente.", + "errorBoundary.details": "Erro", + "errorBoundary.reload": "Recarregar", + "routing.title": "Inteligência de roteamento (beta)", + "routing.subtitle": "Perfis de política, avaliação em dry-run e análises de roteamento com base em fontes.", + "routing.loadFailed": "Não foi possível carregar os dados de roteamento", + "routing.empty": "Nenhum perfil de roteamento configurado. Adicione `routingProfiles` ao config.json.", + "routing.revision": "rev", + "routing.detail": "Perfil", + "routing.createProfile": "Criar perfil", + "routing.dryRunError": "Falha no dry-run (HTTP {status})", + "routing.removeConfirm": "Remover o perfil {id}?", + "routing.unknownEvidence.allow": "permitir", + "routing.unknownEvidence.penalize": "penalizar", + "routing.unknownEvidence.exclude": "excluir", + "routing.removeCandidate": "Remover candidato {provider}/{model}", + "routing.candidates": "Candidatos", + "routing.require": "Requisitos obrigatórios", + "routing.optimize": "Pesos de otimização", + "routing.limits": "Limites", + "routing.unknownEvidence": "Política para evidência desconhecida", + "routing.compatibility.title": "Política de compatibilidade", + "routing.compatibility.enabled": "Exigir evidência do Compatibility Lab", + "routing.compatibility.requiredSuites": "Suítes obrigatórias", + "routing.compatibility.loadingCatalog": "Carregando catálogo do lab…", + "routing.compatibility.catalogUnavailable": "Catálogo do lab indisponível — informe os ids das suítes manualmente no config.json.", + "routing.compatibility.layer.protocol_conformance": "Conformidade de protocolo", + "routing.compatibility.layer.live_route_compatibility": "Compatibilidade de rota ao vivo", + "routing.compatibility.minStatus": "Status mínimo de compatibilidade", + "routing.none": "nenhum", + "routing.unavailable": "–", + "routing.dryRun": "Avaliação em dry-run", + "routing.dryRunContext": "Janela de contexto da requisição (tokens)", + "routing.dryRunTools": "A requisição exige ferramentas", + "routing.dryRunImage": "A requisição exige entrada de imagem", + "routing.dryRunStructured": "A requisição exige saída estruturada", + "routing.dryRunRun": "Avaliar candidatos", + "routing.candidate": "Candidato", + "routing.eligible": "Elegível", + "routing.exclusions": "Exclusões", + "routing.costCap": "Teto de custo", + "routing.capOutcome.satisfied": "dentro do limite", + "routing.capOutcome.exceeded": "acima do limite", + "routing.capOutcome.unknown-allowed": "desconhecido (permitido)", + "routing.capOutcome.unknown-excluded": "desconhecido (excluído)", + "routing.exclusion.capability-unsatisfied": "capacidade não atendida", + "routing.exclusion.unknown-capability": "capacidade desconhecida", + "routing.exclusion.cost-limit": "acima do teto de custo", + "routing.exclusion.cost-limit-unknown": "custo desconhecido sob o teto", + "routing.exclusion.cooldown": "em espera", + "routing.exclusion.unknown-health": "saúde desconhecida", + "routing.exclusion.unknown-quota": "cota desconhecida", + "routing.exclusion.unknown-price": "preço desconhecido", + "routing.exclusion.other": "exclusão: {code}", + "routing.score": "Pontuação", + "routing.selected": "selecionado", + "routing.yes": "sim", + "routing.no": "não", + "routing.analytics": "Análises de roteamento", + "routing.analyticsTotal": "Requisições", + "routing.analyticsSuccessRate": "Sucesso", + "routing.analyticsFallbackRate": "Fallback", + "routing.analyticsP50": "p50", + "routing.analyticsP95": "p95", + "routing.analyticsP99": "p99", + "routing.analyticsCooldown": "Falhas de espera", + "routing.analyticsConfidence": "Confiança", + "routing.analyticsTruncated": "histórico truncado", + "routing.analyticsRequests": "Requisições", + "routing.analyticsEmpty": "Ainda não há análises — envie algumas requisições primeiro.", + "startup.title": "Segurança na inicialização", + "startup.subtitle": "Verifique se o Codex consegue alcançar o opencodex após uma reinicialização, antes que o roteamento pelo proxy local vire um loop de reconexão.", + "startup.refresh": "Atualizar", + "startup.backToDashboard": "Voltar ao painel", + "startup.loading": "Verificando a proteção de inicialização…", + "startup.error": "Não foi possível ler a proteção de inicialização.", + "startup.staleData": "A última verificação de inicialização falhou. Os valores abaixo estão desatualizados e não devem ser tratados como prova de proteção.", + "startup.status.native": "Roteamento nativo", + "startup.status.protected": "Protegido contra reinicialização", + "startup.status.atRisk": "Ação necessária", + "startup.summary.native": "O Codex não depende do proxy local", + "startup.summary.protected": "O opencodex estará disponível após a reinicialização", + "startup.summary.atRisk": "O Codex pode perder o acesso aos modelos após a reinicialização", + "startup.riskDetail": "O Codex está fixado no proxy local, mas nenhum serviço persistente ou shim de inicializador íntegro o iniciará novamente.", + "startup.riskDetailCustomLocal": "O Codex aponta para um gateway local personalizado. O opencodex não consegue gerenciar nem verificar o ciclo de reinicialização desse gateway.", + "startup.riskDetailWindowsShim": "O shim do inicializador protege os scripts de CLI compatíveis, mas o Codex Desktop e as execuções diretas de codex.exe podem contorná-lo no Windows.", + "startup.safeDetail": "O roteamento atual e o mecanismo de inicialização são consistentes. Nenhum ocx start manual deve ser necessário após a reinicialização.", + "startup.routing": "Roteamento do Codex", + "startup.routing.proxy": "Proxy local", + "startup.routing.native": "OpenAI nativo", + "startup.routing.customLocal": "Gateway local personalizado", + "startup.routing.customRemote": "Gateway remoto personalizado", + "startup.routing.unknown": "Roteamento desconhecido ou inválido", + "startup.restartProtection": "Proteção contra reinicialização", + "startup.preference": "Inicialização sob demanda", + "startup.enabled": "Ativada", + "startup.disabled": "Desativada", + "startup.protection.desktop": "App desktop", + "startup.desktopHint": "Inicia no login; o app desktop supervisiona o proxy embutido. A propriedade e os processos ativos são verificados.", + "startup.desktopRecovery": "O OpenCodex é dono deste proxy. Reabra o app desktop e marque Iniciar no login. As alterações de serviço e de inicializador ficam desativadas enquanto o app desktop for o dono.", + "startup.protection.service": "Serviço em segundo plano", + "startup.protection.shim": "Shim do inicializador", + "startup.protection.none": "Não instalado", + "startup.details": "Detalhes da proteção", + "startup.service": "Serviço em segundo plano", + "startup.serviceHint": "Inicia no login e reinicia o proxy após uma falha.", + "startup.installed": "Instalado", + "startup.notInstalled": "Não instalado", + "startup.unsupported": "Sem suporte", + "startup.shim": "Shim do inicializador do Codex", + "startup.shimHint": "Executa ocx ensure quando um inicializador de script do Codex compatível é iniciado.", + "startup.healthy": "Íntegro", + "startup.cliOnly": "Somente CLI", + "startup.stale": "Desatualizado", + "startup.viable": "Pronto", + "startup.unhealthy": "Instalado, mas com problemas", + "startup.conflict": "Conflito de serviço", + "startup.installedDisabled": "Instalado, mas desativado", + "startup.install": "Instalar", + "startup.installing": "Instalando…", + "startup.repair": "Reparar", + "startup.repairing": "Reparando…", + "startup.serviceInstalled": "Serviço em segundo plano instalado com sucesso.", + "startup.serviceRepaired": "Serviço em segundo plano reparado com sucesso.", + "startup.shimInstalled": "Shim do inicializador do Codex instalado com sucesso.", + "startup.shimRepaired": "Shim do inicializador do Codex reparado com sucesso.", + "startup.installFailed": "Falha na instalação:", + "startup.tray.title": "Bandeja do sistema do Windows", + "startup.tray.hint": "Instale um ícone de bandeja no login para iniciar, parar e reiniciar o proxy, abrir o painel e ver o status com um clique.", + "startup.tray.login": "Iniciar a bandeja no login do Windows", + "startup.tray.notProtection": "A bandeja é um controlador, não uma proteção contra reinicialização. Ainda é necessário um serviço em segundo plano viável para a recuperação autônoma do proxy.", + "startup.tray.running": "Em execução", + "startup.tray.stopped": "Instalada, oculta", + "startup.tray.stale": "Reparo necessário", + "startup.tray.notInstalled": "Não instalada", + "startup.tray.loading": "Verificando…", + "startup.tray.unavailable": "Status indisponível", + "startup.tray.install": "Instalar e mostrar a bandeja", + "startup.tray.start": "Mostrar ícone da bandeja", + "startup.tray.stop": "Sair do ícone da bandeja", + "startup.tray.uninstall": "Remover bandeja do login", + "startup.tray.error": "A ação da bandeja do Windows falhou. Consulte ocx tray status para mais detalhes.", + "startup.recovery": "Opções de reparo", + "startup.recoveryHint": "Use os instaladores de um clique acima ou copie um comando para reparo manual. O serviço em segundo plano é recomendado para o Codex Desktop e para executáveis do Windows.", + "startup.command.service": "Recomendado: serviço persistente em segundo plano", + "startup.command.shim": "Alternativa: shim do inicializador da CLI", + "startup.command.native": "À prova de falhas: restaurar o roteamento nativo do Codex", + "startup.copy": "Copiar", + "startup.copied": "Copiado", + "startup.recommended": "Reparo recomendado: {cmd}", + "startup.navRisk": "A proteção de inicialização requer atenção", + "startup.codexRuntime.clampHidden": "Algumas opções de esforço de raciocínio foram ocultadas porque o OpenCodex usou o Codex {version}.", + "startup.codexRuntime.clampHiddenWithEfforts": "Algumas opções de esforço de raciocínio foram ocultadas porque o OpenCodex usou o Codex {version} (removidas: {efforts}).", + "startup.codexRuntime.olderBinary": "O OpenCodex está usando um binário do Codex mais antigo ({version}). Há uma instalação mais recente disponível.", + "dash.subtitle": "Status ao vivo do proxy local do opencodex, de seus provedores e dos modelos roteados para o Codex.", + "dash.workspace.overview": "Visão geral", + "dash.workspace.sections": "Seções", + "dash.status": "Status", + "dash.online": "Online", + "dash.offline": "Offline", + "dash.version": "Versão", + "dash.uptime": "Tempo ativo", + "dash.providers": "Provedores", + "dash.tokens30d": "Tokens (30d)", + "dash.coverage": "{pct} de cobertura", + "dash.mem.title": "Observabilidade de memória", + "dash.mem.hint": "Diagnóstico de runtime somente leitura. A memória observada é max(RSS, external, ArrayBuffers), para que o corte do working set no Windows não esconda retenção já alocada.", + "dash.mem.rss": "Conjunto residente (RSS)", + "dash.mem.jsHeap": "Heap JS em uso", + "dash.mem.jsHeapArena": "arena {total}", + "dash.mem.pressure": "Em relação ao limite de alerta", + "dash.mem.pressureOf": "{pct}% do limite", + "dash.mem.pressureUnknown": "Nenhum limite informado", + "dash.mem.jscHeap": "Heap JSC", + "dash.mem.external": "Externa", + "dash.mem.arrayBuffers": "ArrayBuffers", + "dash.mem.observed": "Observada", + "dash.mem.runtime": "Contadores de runtime", + "dash.mem.growth": "Deriva observada / hora", + "dash.mem.perHour": "/h", + "dash.mem.store": "Armazenamento de continuação", + "dash.mem.storeHint": "Cache de previous_response_id do proxy. Um total de bytes crescente com um heap crescente aponta para retenção de conversas, e não para o alocador do runtime.", + "dash.mem.storeEntries": "Entradas", + "dash.mem.storeTotal": "Total", + "dash.mem.storeLargest": "Maior", + "dash.mem.storeOldest": "Mais antiga", + "dash.mem.threshold": "Limite de alerta", + "dash.mem.lastWarn": "Último alerta", + "dash.mem.never": "Nunca", + "dash.mem.details": "Detalhes", + "dash.mem.unavailable": "Diagnóstico de memória indisponível (proxy mais antigo).", + "dash.mem.inFlight": "Requisições em andamento", + "dash.mem.restart": "Esvaziar e reiniciar", + "dash.mem.restartConfirm": "Aguarde {count} requisição(ões) em andamento e reinicie (até {seconds}s; as requisições restantes são cortadas ao esgotar o tempo).", + "dash.mem.draining": "Esvaziando {count} requisição(ões)… reiniciando ao concluir", + "dash.mem.reconnecting": "Proxy reiniciando… aguardando reconexão", + "dash.mem.restartFailed": "Falha ao esvaziar e reiniciar. Verifique se o proxy está em execução.", + "dash.mem.restartNoSupervisor": "Nenhuma proteção contra reinicialização detectada. O proxy pode permanecer parado após a reinicialização, a menos que você o inicie novamente.", + "dash.activeProviders": "Provedores ativos", + "dash.noProviders": "Nenhum provedor configurado. Execute {cmd}.", + "dash.col.name": "Nome", + "dash.col.adapter": "Adaptador", + "dash.col.baseUrl": "URL base", + "dash.col.model": "Modelo", + "dash.modelsNoResults": "Nenhum modelo corresponde à sua busca.", + "dash.availableModels": "Modelos disponíveis", + "dash.noModels": "Nenhum modelo encontrado. Verifique as chaves de API dos provedores.", + "dash.cannotConnect": "Não foi possível conectar ao proxy. Ele está em execução?", + "dash.runStart": "Execute {cmd} para iniciar o proxy.", + "dash.stop": "Parar proxy", + "dash.stopConfirm": "Parar o proxy e restaurar o Codex nativo?", + "dash.stopFailed": "Falha ao parar o proxy (HTTP {status}).", + "dash.stopStillRunning": "O proxy ainda está respondendo, então não foi parado.", + "dash.stopUnknown": "O proxy não confirmou a parada. Verifique se ele ainda está em execução antes de tentar novamente.", + "dash.maSwitchFailed": "Falha na troca de modo (HTTP {status}).", + "dash.maNetworkError": "Erro de rede — o proxy está em execução?", + "dash.stopping": "Parando…", + "dash.actions": "Proxy", + "dash.codexRestart": "Recarregar modelos do Codex", + "dash.codexRestarting": "Parando…", + "dash.codexRestartConfirm": "Parar os app-servers do Codex para que recarreguem a lista de modelos? Qualquer turno do Codex em andamento será interrompido, e o Codex não é reiniciado sozinho — reabra-o depois.", + "dash.codexRestartDone": "{count} app-server(s) do Codex parado(s). Reabra o Codex para carregar a lista de modelos atual.", + "dash.codexRestartNothing": "Nenhum app-server do Codex em execução. A próxima inicialização lerá a lista de modelos atual.", + "dash.codexRestartUnknown": "Não foi possível listar os processos, então nada foi parado.", + "dash.codexRestartPartial": "{count} app-server(s) não encerrou(aram). Pare-os manualmente se a lista de modelos continuar desatualizada.", + "dash.codexRestartFailed": "Falha ao recarregar os modelos do Codex (HTTP {status}).", + "dash.codexRestartUnreachable": "Não foi possível alcançar o proxy.", + "dash.codexRestartMalformed": "O proxy retornou uma resposta inesperada.", + "dash.codexRestartTimeout": "O proxy não respondeu a tempo. Ele ainda pode estar parando os app-servers.", + "models.staleBanner": "O Codex está mostrando uma lista de modelos mais antiga que este catálogo. Reinicie o Codex para recarregá-la.", + "dash.codexAutoStart": "Iniciar o opencodex com o Codex", + "dash.codexAutoStartHint": "Permite que um shim de inicializador instalado execute ocx ensure. Esta configuração não instala proteção contra reinicialização; confira Segurança na inicialização para ver o estado efetivo.", + "dash.searchModel": "Modelo do sidecar de busca", + "dash.searchModelHint": "Modelo usado para web_search em modelos roteados que não são da OpenAI. Requer login no ChatGPT.", + "dash.searchReasoning": "Esforço de raciocínio da busca", + "dash.visionModel": "Modelo do sidecar de visão", + "dash.visionModelHint": "Modelo usado para descrever imagens para modelos roteados somente de texto. Requer login no ChatGPT.", + "dash.webSearchSidecar": "Sidecar de busca na web", + "dash.webSearchSidecarHint": "Escolha o backend e o modelo usados para busca na web em modelos roteados.", + "dash.webSearchOff": "Desligado", + "dash.webSearchCodexSync": "Salvo. A configuração do Codex não foi reescrita — execute Sincronizar modelos para aplicá-la.", + "dash.webSearchStream": "Transmitir respostas ao vivo", + "dash.webSearchStreamHint": "Transmite ao vivo o texto inicial e o raciocínio do modelo até ele decidir por uma chamada de ferramenta; o restante do turno continua em buffer para a interceptação da busca. O texto escrito antes de uma busca pode se repetir parcialmente.", + "dash.visionSidecar": "Sidecar de visão", + "dash.visionSidecarHint": "Escolha o backend e o modelo usados para descrever imagens para modelos roteados somente de texto.", + "dash.visionOff": "Desligado", + "dash.visionAdvanced": "Configurações avançadas", + "dash.visionMaxDescriptions": "Máximo de descrições por turno", + "dash.visionMaxDescriptionsInvalid": "Informe um número inteiro positivo.", + "dash.visionTimeout": "Tempo limite", + "dash.visionTimeoutInvalid": "Informe um número inteiro de {min} a {max} milissegundos.", + "dash.visionAdvancedPopover": "Configurações avançadas de visão", + "compactionRouting.title": "Roteamento de compactação", + "compactionRouting.description": "Escolha um modelo para as requisições de compactação do Codex e quais gatilhos ele cobre. As mensagens seguintes mantêm as configurações da conversa.", + "compactionRouting.model": "Modelo de compactação", + "compactionRouting.effort": "Esforço de raciocínio", + "compactionRouting.triggers": "Aplica-se a", + "compactionRouting.triggersManual": "Somente /compact manual", + "compactionRouting.triggersBoth": "Manual e automática", + "compactionRouting.triggersAuto": "Somente automática", + "compactionRouting.currentModel": "Usar o modelo da conversa", + "compactionRouting.currentEffort": "Manter o esforço da requisição", + "compactionRouting.effortHint": "O raciocínio se aplica onde o endpoint de compactação der suporte. O modelo precisa aceitar a conversa completa.", + "compactionRouting.dataNotice": "Uma requisição de compactação coberta envia a conversa inteira ao provedor do modelo selecionado para resumo, mesmo quando a conversa roda em outro provedor.", + "compactionRouting.autoNotice": "A compactação automática roda por conta própria, então uma conversa longa pode ser enviada a esse provedor sem que você peça.", + "compactionRouting.providerWarning": "Com esta configuração, toda requisição de compactação coberta envia o conteúdo completo da conversa para {provider} para resumo.", + "compactionRouting.comboWarning": "Com esta configuração, toda requisição de compactação coberta envia o conteúdo completo da conversa ao combo {combo} para resumo. O combo escolhe um de seus destinos ({providers}) pela estratégia de roteamento, mas após uma falha recuperável pode repetir a mesma requisição com a conversa completa em outro destino. Assim, um ou mais provedores de destino podem receber a conversa.", + "compactionRouting.comboProvidersUnknown": "seus provedores de destino configurados", + "compactionRouting.loadFailed": "Não foi possível carregar as configurações de compactação.", + "compactionRouting.saved": "Configurações de compactação salvas.", + "compactionRouting.saveFailed": "Não foi possível salvar. Suas alterações continuam aqui; tente novamente.", + "memoryModels.title": "Roteamento de memória", + "memoryModels.description": "O Codex grava memórias após o término de uma sessão. Escolha um modelo para cada fase ou mantenha a rota existente.", + "memoryModels.infoLabel": "O que são Extração e Consolidação?", + "memoryModels.info": "O Codex transforma sessões encerradas em memória em duas etapas. A Extração lê uma sessão encerrada e anota o que aconteceu: uma nota por sessão, então faz muitas chamadas pequenas. A Consolidação pega essas notas e as grava nos arquivos de memória que o Codex lê no início das próximas sessões. Roda raramente, mas edita arquivos. Cada etapa pede seu próprio modelo, por isso aparecem separadas aqui.", + "memoryModels.extract": "Extração", + "memoryModels.extractHint": "Resume cada sessão encerrada em uma memória bruta. Roda uma vez por sessão.", + "memoryModels.consolidation": "Consolidação", + "memoryModels.consolidationHint": "Mescla as memórias brutas nos arquivos de memória que o Codex lê depois. Roda raramente e edita arquivos.", + "memoryModels.model": "Modelo", + "memoryModels.effort": "Esforço de raciocínio", + "memoryModels.off": "Desativado", + "memoryModels.defaultEffort": "Padrão do Codex", + "memoryModels.dataNotice": "O modelo escolhido recebe a entrada daquela fase: a sessão encerrada na Extração e as memórias brutas na Consolidação.", + "memoryModels.accountNotice": "Somente uma fase é roteada aqui; a outra mantém a rota existente. O Shadow Call Intercept também pode enviar as chamadas de memória dessa fase ao modelo configurado nele.", + "memoryModels.loadFailed": "Não foi possível carregar as configurações de memória.", + "memoryModels.saved": "Configurações de memória salvas.", + "memoryModels.saveFailed": "Não foi possível salvar. Suas alterações continuam aqui; tente novamente.", + "dash.shadowCallIntercept": "Shadow Call Intercept", + "dash.shadowCallInterceptHint": "Intercepta as chamadas auxiliares em segundo plano do Codex App ({models}) para geração de títulos e mensagens de commit e as redireciona ao modelo escolhido.", + "dash.shadowCallWarning": "⚠ Quando ativado, TODAS as requisições para {models} serão substituídas pelo modelo selecionado.", + "dash.shadowCallOriginal": "Original", + "dash.shadowCallModel": "Modelo substituto", + "dash.shadowCallTooltip": "O Codex App faz chamadas auxiliares em segundo plano para geração de título de thread, geração de mensagem de commit e orquestração de skills. O modelo auxiliar mudou entre versões do cliente, então o opencodex intercepta todos os modelos deste conjunto: {models}. Ative para redirecionar essas chamadas ao modelo escolhido.", + "models.shadowCallIntercept": "Shadow Call Intercept", + "models.shadowCallInterceptHint": "Intercepta as chamadas auxiliares em segundo plano do Codex App ({models}) para títulos e mensagens de commit e as redireciona ao modelo escolhido.", + "dash.sidecarBackend": "Backend", + "dash.sidecarModel": "Modelo", + "dash.backendAuto": "Automático", + "dash.backendOpenAI": "OpenAI", + "dash.backendAnthropic": "Anthropic", + "dash.sidecarSaved": "Configurações do sidecar salvas. Aplicadas na próxima requisição.", + "dash.sidecarSaveFailed": "Falha ao salvar as configurações do sidecar.", + "dash.injectionLabel": "Delegação a subagentes", + "dash.injectionHint": "Escolha o modelo para o qual o Codex deve delegar o trabalho de subagentes. As duas opções abaixo definem onde essa escolha é usada.", + "dash.injectionManage": "Abrir configurações", + "dash.syncCodexSubagentDefaults": "Salvar também como padrão do Codex", + "dash.syncCodexSubagentDefaultsHint": "Ativado, a escolha acima é gravada na configuração do próprio Codex, e novas tarefas também começam com esse modelo. Desativado, ela fica salva apenas aqui. Entra em vigor na próxima sincronização ou reinicialização, e suas configurações [agents] escritas à mão são preservadas.", + "dash.multiAgentGuidance": "Orientar o Codex a dividir o trabalho", + "dash.multiAgentGuidanceHint": "Envia uma nota curta dizendo ao Codex como repassar trabalho a subagentes. Na v2, ela cita os modelos que podem ser usados e qual preferir; na v1, só se aplica com esforço de raciocínio max ou ultra. Desativado, nenhuma nota é adicionada.", + "dash.injectionNone": "Nenhum", + "dash.injectionEffortLabel": "Esforço de raciocínio", + "dash.injectionEffortNone": "Padrão do modelo", + "dash.effortCapLabel": "Limite de esforço ultra na V2", + "dash.subagentEffortCapLabel": "Limite de esforço de subagentes na V2", + "dash.effortCapHelp": "Limita o esforço de raciocínio dos turnos em modo ultra da V2. Quando definido, requisições de esforço máximo (do modo ultra) são limitadas ao nível selecionado. O limite de subagentes se aplica apenas aos agentes filhos criados. Os limites só reduzem o esforço, nunca o aumentam. Se um modelo não suportar o nível limitado, ele desce para o nível suportado mais próximo.", + "dash.effortCapNone": "Sem limite", + "dash.maintenance": "Manutenção", + "dash.maintenanceHint": "Atualize o catálogo de modelos do Codex ou instale uma versão mais nova do opencodex.", + "dash.syncModels": "Sincronizar modelos", + "dash.syncModelsHint": "Reescreve o catálogo de modelos do Codex a partir dos provedores conectados.", + "dash.syncRun": "Sincronizar agora", + "dash.syncing": "Sincronizando…", + "dash.syncOk": "Sincronização concluída. {count} modelo(s) adicionado(s).", + "dash.syncStaleHint": "Se o Codex ainda mostrar uma lista antiga, reinicie o app-server de longa duração dele ({cmd}).", + "dash.syncFailed": "Falha na sincronização: {error}", + "dash.projectConfigTitle": "A configuração de Codex do projeto ignora o OpenCodex", + "dash.projectConfigHint": "Estas configurações locais do repositório substituem o proxy do OpenCodex (por exemplo, roteando direto para o OpenCode Go). Remova-as para que o roteamento de ~/.codex/config.toml valha nesse projeto.", + "dash.checkUpdate": "Verificar atualização", + "dash.updateTitle": "Atualizar o opencodex", + "dash.updateDesc": "Verifica no npm o canal selecionado e depois permite escolher se o proxy será reiniciado após a instalação.", + "dash.updateChannel": "Canal", + "dash.updateChecking": "Verificando atualizações…", + "dash.updateInstalled": "Instalada", + "dash.updateLatest": "Mais recente", + "dash.updateAvailable": "Atualização disponível", + "dash.updateCurrent": "Atualizado", + "dash.updateCommand": "Comando", + "dash.updateSource": "Esta é uma cópia do código-fonte. Atualize-a pelo terminal com o comando exibido.", + "dash.updateUnavailable": "Não foi possível ler a versão mais recente no npm. Tente novamente mais tarde.", + "dash.updateRetry": "Tentar novamente", + "dash.updateRecheck": "Verificar de novo", + "dash.updateCannotAuto": "A atualização com um clique não está disponível ({reason}).", + "dash.updateReason.source_checkout": "cópia do código-fonte", + "dash.updateReason.latest_unavailable": "registro do npm inacessível", + "dash.updateReason.already_latest": "já está na versão mais recente", + "dash.updateReason.externally_managed": "gerenciado externamente pelo mise; execute o comando exibido", + "dash.updateReason.external_ownership_invalid": "os metadados de propriedade do mise estão ilegíveis ou inconsistentes", + "dash.updateReason.unknown": "atualização indisponível", + "dash.updateRestart": "Reiniciar após atualizar", + "dash.updateRestartHint": "Recomendado. A GUI atual continua executando o código antigo até o proxy reiniciar.", + "dash.runUpdate": "Atualizar", + "dash.updateReconnecting": "Aguardando o proxy reiniciado…", + "dash.updateStatus.running": "Atualizando o opencodex.", + "dash.updateStatus.restarting": "Atualização instalada. Reiniciando o proxy.", + "dash.updateStatus.succeeded": "Atualização concluída.", + "dash.updateVersionTransition": "{currentVersion} -> {latestVersion}.", + "dash.updateStatus.failed": "Falha na atualização.", + "prov.subtitle": "Configure os provedores upstream para os quais o opencodex roteia o Codex. Faça login com uma conta, adicione um provedor ou edite a configuração bruta.", + "prov.add": "Adicionar provedor", + "prov.editJson": "Editar JSON", + "prov.accountLogin": "Login de conta", + "prov.noOauth": "Nenhum provedor OAuth disponível.", + "prov.loggedIn": "conectado", + "prov.notLoggedIn": "não conectado", + "prov.logout": "Sair", + "prov.login": "Entrar", + "prov.loginWith": "Entrar com {provider}", + "prov.waitingBrowser": "Aguardando o navegador…", + "prov.didntOpen": "Não abriu? Clique aqui", + "prov.copyLink": "Copiar link", + "prov.dontOpenBrowser": "Não abrir um navegador na máquina do proxy", + "prov.dontOpenBrowserHint": "Útil para usar outro perfil de navegador ou quando o painel não está na máquina do proxy.", + "prov.linkCopied": "Copiado", + "prov.linkCopyUnavailable": "Área de transferência indisponível", + "prov.deviceCode": "Código do dispositivo", + "prov.copyCode": "Copiar código", + "prov.copyCodeAndOpen": "Copiar código e abrir", + "prov.openSignInPage": "Abrir página de login", + "prov.browserLaunchFailed": "O navegador não abriu automaticamente. Abra a página de login abaixo ou copie o link.", + "prov.codeCopied": "Código copiado", + "prov.editAlias": "Editar apelido", + "prov.aliasPrompt": "Nome de exibição (deixe vazio para limpar)", + "prov.aliasSaved": "Apelido salvo", + "prov.aliasSaveFailed": "Não foi possível salvar o apelido", + "prov.aliasInvalid": "Use no máximo 80 caracteres, sem caracteres de controle.", + "prov.accountId": "ID", + "prov.pasteRedirect": "Cole a URL de redirecionamento ou o código", + "prov.pasteRedirectHint": "Se o navegador mostrar um erro de localhost, copie a URL completa da barra de endereços e cole aqui (ou cole o código de autorização).", + "prov.pasteSubmit": "Enviar", + "prov.pasteSubmitting": "Enviando…", + "prov.pasteOk": "Código enviado — concluindo o login…", + "prov.pasteFail": "Não foi possível enviar o código: {error}", + "prov.port": "Porta", + "prov.default": "Padrão", + "prov.loadingConfig": "Carregando…", + "prov.saved": "Salvo! Reinicie o proxy para aplicar.", + "prov.loadConfigFail": "Falha ao carregar a configuração", + "prov.invalidJson": "JSON inválido", + "prov.saveFailed": "Falha ao salvar", + "prov.loginFailStart": "O login de {provider} não conseguiu iniciar", + "prov.loginError": "Erro no login de {provider}: {error}", + "prov.loginRequestFail": "A requisição de login de {provider} falhou", + "prov.loginCancelled": "Login de {provider} cancelado", + "prov.loginTimeout": "O login de {provider} expirou — o navegador foi fechado ou o processo nunca terminou. Tente novamente.", + "prov.loginOk": "Login em {provider} concluído. Execute {cmd} (ou ele se aplica ao vivo) para listar seus modelos.", + "prov.loginSameAccount": "Ainda é a mesma conta de {provider} — troque de conta no navegador e tente adicionar a conta novamente.", + "oauthTos.highTitle": "{provider}: risco do OAuth de assinatura", + "oauthTos.elevatedTitle": "{provider}: ponte OAuth não oficial", + "oauthTos.anthropicBody": "Isto conecta uma assinatura do Claude por meio do proxy de terceiros do OpenCodex. A Anthropic não endossou esta conexão nem o pooling automatizado de contas; o acesso depende dos termos e da fiscalização vigentes.", + "oauthTos.highBody": "O OpenCodex conecta {provider} por um caminho OAuth de terceiros. O uso sem suporte pode levar a limites de acesso ou suspensão.", + "oauthTos.elevatedBody": "O OpenCodex conecta {provider} por um caminho OAuth não oficial. Use o cliente oficial sempre que possível; tráfego incomum ou automatizado pode ser tratado como abuso, e o acesso pode ser limitado ou suspenso.", + "oauthTos.saferPath": "Opção mais segura: configure uma chave de API no OpenCodex.", + "oauthTos.acknowledge": "Entendo o risco e quero continuar com OAuth mesmo assim.", + "oauthTos.continue": "Continuar com OAuth", + "oauthTos.anthropicTitle": "Conexão de assinatura do Claude", + "oauthTos.anthropicConditions": "Use uma conta que você possua ou esteja autorizado a usar. Usar o cliente Claude Code genuíno com uma pessoa supervisionando a sessão é diferente de enviar tokens de assinatura por outros clientes. O OpenCodex também oferece suporte a esses outros clientes, portanto esta conexão não garante que todo uso seja permitido. As integrações com o Agent SDK compatíveis são tratadas à parte.", + "oauthTos.anthropicSaferPath": "Para outros clientes ou automação sem supervisão, configure uma chave de API da Anthropic.", + "oauthTos.anthropicAcknowledge": "Entendo que esta é uma conexão de assinatura de terceiros e verificarei os termos aplicáveis.", + "oauthTos.anthropicContinue": "Continuar com a assinatura do Claude", + "subagentSurface.selectionTitle": "Mudar a superfície de subagentes para {mode}?", + "subagentSurface.selectionBody": "Em {mode}, os modelos do ChatGPT que usam a superfície v2 — Sol e Terra na base, todos os modelos na v2 — entregam sua tarefa a um modelo roteado, como Grok ou Claude, criptografada para o backend do ChatGPT, e o modelo roteado não consegue lê-la. Essa delegação falha com unreadable_encrypted_agent_task até que seja corrigido upstream. A v1 delega entre provedores de forma confiável.", + "subagentSurface.advisoryTitle": "A v1 agora é a superfície de subagentes padrão", + "subagentSurface.advisoryBody": "Esta instalação roda {mode}, em que um modelo do ChatGPT na superfície v2 entrega a um modelo roteado uma tarefa criptografada que ele não consegue ler, e a delegação entre provedores falha. Recomendamos a v1 até que isso seja corrigido upstream. Sua configuração atual não muda até você escolher.", + "subagentSurface.continue": "Continuar", + "subagentSurface.switchToV1": "Mudar para a v1", + "subagentSurface.learnMore": "Por que isso falha", + "prov.logoutOk": "Sessão de {provider} encerrada.", + "prov.logoutFail": "Não foi possível sair de {provider}. O estado da sua conta não mudou.", + "prov.removed": "\"{name}\" removido.", + "prov.removedDefault": "\"{name}\" removido. O provedor padrão agora é \"{defaultProvider}\".", + "prov.removedShadowTarget": "\"{name}\" removido. O Shadow Call Intercept ainda aponta para \"{model}\", então as chamadas auxiliares vão falhar até você escolher outro destino.", + "prov.removeFail": "Falha ao remover \"{name}\".", + "prov.removeLastProvider": "Você não pode remover este provedor quando nenhum outro provedor habilitado pode se tornar o padrão.", + "prov.removeHasDependentCombos": "Remova ou atualize antes estes combos dependentes: {combos}.", + "prov.setDefault": "Definir como padrão", + "prov.setDefaultSuccess": "\"{name}\" agora é o provedor padrão.", + "prov.setDefaultFail": "Não foi possível definir \"{name}\" como provedor padrão.", + "prov.defaultDisabled": "Habilite este provedor antes de torná-lo o padrão.", + "prov.updateFail": "Não foi possível atualizar este provedor.", + "prov.networkError": "Erro de rede. Verifique se o proxy está em execução e tente novamente.", + "prov.added": "\"{name}\" adicionado. Já está ativo — execute {cmd} (ou reinicie) para listar seus modelos no seletor do Codex.", + "prov.modelsNoticeTitle": "Escolha os modelos", + "prov.modelsNoticeChecking": "Verificando a lista de modelos. Alternar modelos não desativa o provedor.", + "prov.modelsNoticePending": "A lista inicial de modelos ainda não foi confirmada. Os modelos ficam ocultos até a descoberta terminar.", + "prov.modelsNoticeOff": "Todas as chaves de modelo foram DESLIGADAS no registro. Ative os modelos desejados na página Modelos.", + "prov.modelsNoticeReady": "Escolha quais modelos aparecem na página Modelos. Alternar modelos não desativa o provedor.", + "prov.modelsNoticeFailed": "O provedor foi salvo, mas não foi possível atualizar a lista de modelos. Tente novamente.", + "prov.modelsNoticeCount": "{count} modelos", + "prov.modelsNoticeOpen": "Abrir Modelos", + "models.initialSelectionPending": "Descoberta inicial pendente", + "prov.removeConfirm": "Remover o provedor \"{name}\"? Os modelos dele somem do seletor do Codex.", + "prov.hasApiKey": "chave de API configurada", + "prov.hasHeaders": "cabeçalhos personalizados configurados", + "prov.accounts": "Contas ({n})", + "prov.accountsAria": "Alternar contas de {name}", + "prov.accountActive": "Ativa", + "prov.accountReauth": "Entrar de novo", + "prov.reauthenticate": "Reautenticar", + "prov.reauthAccountMissing": "A conta selecionada não foi encontrada após o login", + "prov.reauthIdentityMismatch": "A conta conectada não corresponde à conta selecionada", + "prov.accountAdd": "Adicionar conta", + "prov.accountNoLabel": "conta {id}", + "prov.accountSwitchTitle": "Usar esta conta", + "prov.accountSwitched": "Trocado para {email}.", + "prov.accountSwitchFail": "Falha ao trocar de conta", + "prov.accountRemoved": "{email} removida.", + "prov.accountRemoveFail": "Não foi possível remover {email}. A conta não foi alterada.", + "prov.accountRemoveAria": "Remover {email}", + "prov.accountRemoveConfirm": "Remover a conta {email}? O login dela é excluído deste proxy.", + "prov.keyAdd": "Adicionar chave de API", + "prov.keyAdded": "Chave de API adicionada a {name}.", + "prov.keyAddFail": "Falha ao adicionar a chave de API", + "prov.keyPlaceholder": "Cole a chave de API", + "prov.keySwitchTitle": "Usar esta chave", + "prov.keySwitched": "Trocado para a chave {key}.", + "prov.keySwitchFail": "Falha ao trocar de chave", + "prov.keyRemoved": "Chave {key} removida.", + "prov.keyRemoveAria": "Remover a chave {key}", + "prov.keyRemoveConfirm": "Remover a chave de API {key}? Ela é excluída da configuração deste proxy.", + "prov.activeBadge": "Ativa", + "prov.disabledBadge": "Desativado", + "prov.defaultBadge": "Padrão", + "prov.enable": "Habilitar", + "prov.disable": "Desabilitar", + "prov.enabled": "\"{name}\" habilitado. Seus modelos podem voltar a aparecer no Codex.", + "prov.disabled": "\"{name}\" desabilitado. As configurações são mantidas, mas os modelos ficam ocultos.", + "prov.disabledShadowTarget": "\"{name}\" desabilitado. O Shadow Call Intercept ainda aponta para \"{model}\", então as chamadas auxiliares vão falhar até você escolher outro destino ou reabilitar \"{name}\".", + "prov.enableFail": "Falha ao habilitar \"{name}\".", + "prov.disableFail": "Falha ao desabilitar \"{name}\".", + "prov.enableAria": "Habilitar o provedor {name}", + "prov.disableAria": "Desabilitar o provedor {name}", + "prov.defaultCannotDisable": "O provedor padrão não pode ser desabilitado", + "prov.openaiAccountMode": "Modo de conta do Codex", + "prov.openaiModePool": "Pool", + "prov.openaiModeDirect": "Direto", + "prov.openaiPoolDesc": "Padrão. Alterna entre o login principal e as contas adicionadas usando afinidade, cota, cooldown e failover.", + "prov.openaiDirectDesc": "Usa somente o login atual/principal do Codex. As contas armazenadas no pool não são lidas nem alternadas.", + "prov.openaiModeSaved": "Modo de conta da OpenAI alterado para {mode}.", + "prov.openaiModeSaveFailed": "Não foi possível alterar o modo de conta da OpenAI.", + "prov.openaiApiDesc": "Usa uma chave de API da OpenAI e nunca usa as credenciais da conta do Codex.", + "prov.manageCodexAccounts": "Gerenciar contas do Codex", + "prov.openaiApiMissing": "Chave de API obrigatória", + "prov.openaiApiSetup": "Configurar chave de API", + "models.tab.catalog": "Modelos", + "models.tab.combos": "Combos", + "models.tab.compatibility": "Compatibilidade", + "models.tab.routing": "Roteamento (beta)", + "models.tabsLabel": "Superfícies de modelos", + "models.subtitle.combos": "Grupos ordenados de modelos que respondem como um único id. Encadeie destinos com failover ou distribua a carga com uma estratégia de balanceamento.", + "models.subtitle.compatibility": "Matriz somente leitura de veredictos de compatibilidade, com base em evidências de projeção de laboratório.", + "models.subtitle.routing": "Perfis de política, avaliação em modo de simulação (dry-run) e análises de roteamento com base em fontes.", + "models.subtitle": "Alterne quais modelos o Codex enxerga — passthrough nativo do GPT e provedores roteados, agrupados por provedor (clique em um cabeçalho para recolher). Modelos ocultos ficam fora do catálogo e do seletor de modelos, mas continuam acessíveis diretamente pelo id exato.", + "models.delivery.title": "Como as alterações chegam ao Codex", + "models.delivery.chip.saved": "Salvo aqui", + "models.delivery.chip.savedHub": "Salvo no hub", + "models.delivery.chip.synced": "Sincronizado {time}", + "models.delivery.chip.syncedUnknown": "Nenhuma sincronização registrada", + "models.delivery.chip.loaded": "O Codex carrega ao reiniciar", + "models.delivery.saved.title": "Salvo neste proxy", + "models.delivery.saved.body": "Salvar nesta página reescreve o catálogo de modelos que este proxy OpenCodex entrega ao Codex, que é a lista exibida no seletor de modelos do Codex. A confirmação após salvar significa que esta etapa foi concluída.", + "models.delivery.savedHub.title": "Salvo no hub", + "models.delivery.savedHub.body": "Esta máquina está conectada a um hub OpenCodex compartilhado, então salvar aqui altera o catálogo do hub. A confirmação após salvar significa que o hub o recebeu; esta máquina ainda precisa baixá-lo.", + "models.delivery.synced.title": "Sincronizado nesta máquina", + "models.delivery.synced.bodyAt": "Esta máquina baixou o catálogo do hub pela última vez em {time} e o gravou onde o Codex lê. Esse horário indica apenas quando o download ocorreu. Se você salvou no hub depois disso, esta máquina mantém a lista anterior até a próxima sincronização.", + "models.delivery.synced.bodyUnknown": "Esta máquina ainda não tem registro de download do catálogo do hub, então o Codex pode estar lendo uma lista antiga. A próxima sincronização fará o download.", + "models.delivery.loaded.title": "Carregado pelo Codex", + "models.delivery.loaded.body": "O Codex lê o catálogo ao iniciar, então uma janela do Codex que já estava aberta mantém a lista de modelos antiga. O OpenCodex não consegue ver dentro de um Codex em execução para saber qual lista ele usa. Quando consegue perceber que o Codex iniciou antes da sua alteração, um aviso de reinicialização aparece acima das abas.", + "models.delivery.hint": "Se um modelo que você ativou não aparecer no Codex, clique em Recarregar modelos do Codex no canto superior direito desta página e reabra o Codex.", + "models.nativeGroupLabel": "OpenAI nativo", + "models.nativeHint": "Os modelos em passthrough usam a opção de conta Pool ou Direto selecionada em Provedores. Desativar um deles o oculta do seletor do Codex (a entrada do catálogo é mantida, então reativar restaura exatamente como estava). Adicionar um modelo aqui registra um seletor roteado `openai/`, não um novo id de passthrough simples.", + "models.active": "{active}/{total} visíveis", + "models.workspace.providers": "Provedores", + "models.workspace.allProviders": "Todos os provedores", + "models.workspace.mainAria": "Detalhes do modelo", + "models.allOn": "Ativar todos", + "models.allOff": "Desativar todos", + "models.presetLabel": "Modelos", + "models.presetMode_preset": "Preset", + "models.presetMode_all": "Todos", + "models.presetMode_custom": "Personalizado", + "models.presetSummary": "{count} de {total} exibidos — preset básico v{version}", + "models.presetUpdateAvailable": "Preset v{version} disponível", + "models.presetAppliedToast": "{provider}: preset aplicado — {count} modelos selecionados", + "models.presetClearedToast": "{provider}: exibindo todos os modelos", + "models.presetEmpty": "{provider}: o preset não correspondeu a nenhum modelo — seleção inalterada", + "models.presetConfirmReplace": "Substituir sua seleção pelo preset de {count} modelos?", + "models.cap350k": "Limite de 350k", + "models.capApplied": "Limite de contexto aplicado — vale a partir do próximo turno do Codex.", + "models.capSaveFailed": "Falha ao salvar o limite de contexto", + "models.contextCapped": "limite de 350k", + "models.contextCapLabel": "Janela padrão / limite", + "models.v2Label": "Subagente", + "models.shadowCallOriginal": "{models} →", + "models.v2DocsLink": "O que são v1 / v2?", + "models.v2Mode_v1": "v1", + "models.v2Mode_default": "base", + "models.v2Mode_v2": "v2", + "models.v2ModeDesc_v1": "Todos os modelos → superfície v1", + "models.v2ModeDesc_default": "Padrões do upstream (sol/terra=v2, luna=v1)", + "models.v2ModeDesc_v2": "Todos os modelos → superfície v2", + "models.keepNativeOnV1": "Manter o ChatGPT na v1", + "models.keepNativeOnV1Hint": "O ChatGPT só criptografa as tarefas filhas da v2 quando um pai nativo do ChatGPT permanece na v2, então Grok e Claude não conseguem lê-las. Ative para manter Sol/Terra na v1 e evitar essa criptografia. Pais roteados continuam na v2.", + "models.v2Help": "Controla a superfície multiagente para todos os modelos.\n\nv1: agente clássico de thread única. Todos os modelos usam a superfície collab da v1.\nbase: padrões do upstream — sol/terra usam a v2, luna usa a v1, os demais seguem a feature flag do codex.\nv2: agente multithread com spawn_agent. Todos os modelos usam a superfície collab da v2.\n\nNa v2, Manter o ChatGPT na v1 deixa Sol/Terra na superfície v1 para que ainda possam acionar Grok ou Claude. O ChatGPT criptografa as tarefas filhas da v2; modelos roteados não conseguem lê-las. Pais roteados permanecem na v2.\n\nAs alterações valem para novas sessões.", + "dash.multiAgent": "Subagente", + "dash.codexDesktopAuthless": "Abrir o Codex sem fazer login", + "dash.codexDesktopAuthlessHint": "Desativado por padrão. Dispensa o login separado do Desktop em conexões locais elegíveis. As credenciais upstream continuam sendo necessárias. Reinicie o Codex após alterar esta configuração. Recursos do Desktop que dependem de conta podem ficar indisponíveis.", + "dash.codexClientCompaction": "Usar compactação no cliente", + "dash.codexClientCompactionHint": "Desativado por padrão; somente loopback autenticado. As próximas compactações armazenam resumos portáveis em texto simples enquanto o roteamento de provedores do OpenCodeX e da V2 permanece ativo; o provedor configurado pode processá-los e consumir cota. O histórico não é alterado, e as threads existentes continuam roteando pelo proxy por meio do override openai_base_url gerenciado pelo OpenCodeX; se você definir essa linha manualmente, ela é mantida, e essas threads seguem o seu destino. O histórico ocx1 existente continua recuperável; recupere uma thread separadamente apenas antes de reproduzi-la no Codex nativo. Reinicie o Codex após alterar esta configuração.", + "dash.codexRemoteHistoryHint": "O roteamento por tabela de provedores pode ocultar threads existentes marcadas como openai em algumas listas remotas do celular. O histórico não é excluído. O cliente remoto precisa listar todos os provedores; esta opção não corrige o filtro desse cliente.", + "models.v2Conflict": "[agents] max_threads está definido — o codex se recusará a iniciar; remova-o do config.toml", + "models.v2Applied": "Modo de subagente atualizado — vale para novas sessões (reinicie o app do Codex para atualizar o seletor)", + "models.v2ThreadsLabel": "Máx. de threads", + "models.v2ThreadsDefault": "padrão (4)", + "models.v2ThreadsApplied": "Limite de threads atualizado — vale para novas sessões", + "models.v2ThreadsInvalid": "O limite de threads deve ser um inteiro >= 1", + "models.v2ThreadsApply": "Aplicar", + "models.capValue": "Padrão {value}", + "models.contextSettings": "Janelas personalizadas", + "models.contextSettingsTitle": "Janelas personalizadas — {provider}", + "models.contextDefault": "Padrão do provedor", + "models.contextModel": "Modelo", + "models.contextModelOverride": "Substituição do modelo", + "models.contextHint": "Informe aqui a janela real do Codex quando já a conhecer. Isso preenche uma janela upstream ausente e só reduz uma janela maior informada. Deixe em branco para usar a Janela padrão / limite do provedor, ou 128k se esse limite estiver desativado.", + "models.contextAutomatic": "Descoberta automática", + "models.contextSaved": "Janelas de contexto atualizadas — valem a partir do próximo turno do Codex.", + "models.contextUnchanged": "Nenhuma alteração de janela de contexto para salvar.", + "models.contextSaveFailed": "Falha ao salvar as janelas de contexto", + "models.contextInvalid": "As janelas de contexto devem ser números inteiros positivos", + "models.contextCappedValue": "limite de {value}", + "models.setAll": "Definir para todos", + "models.setAllHint": "Ativa a janela padrão de {value} para todos os provedores roteados. Relays que omitem context_window / context_length recebem este valor como a janela real do Codex. Use Janelas personalizadas na linha de um provedor para ajustar um modelo manualmente. Provedores nativos não são afetados.", + "models.collapseAll": "Recolher tudo", + "models.expandAll": "Expandir tudo", + "models.orderHint": "A ordem padrão segue as seleções em destaque e a prioridade do catálogo, com ordenação por provedor/modelo para linhas roteadas empatadas. Uma ordem salva do seletor pode sobrepor a ordem de exibição; as chaves de visibilidade filtram quais linhas aparecem. Um cliente pode manter um catálogo antigo até buscá-lo novamente.", + "models.settingsPanel.title": "Configurações do modelo", + "models.settingsPanel.off": "desativado", + "models.settingsPanel.attention": "Requer atenção", + "models.custom": "Personalizado…", + "models.customApply": "Aplicar", + "models.customPlaceholder": "Tokens (ex.: 420000)", + "models.customAdd": "Adicionar modelo personalizado", + "models.customAddTitle": "Adicionar modelo personalizado — {provider}", + "models.customEditTitle": "Editar modelo personalizado — {provider}", + "models.settingsTitle": "Editar modelo — {model}", + "models.settingsContextInherit": "Sem substituição — atualmente {value}", + "models.settingsContextUnknown": "desconhecido", + "models.settingsReload": "Recarregar", + "models.settingsReloaded": "Lista de modelos recarregada. Reabra {model} para ver os valores armazenados.", + "models.settingsNoChange": "Nenhuma configuração alterada para {model}", + "models.settingsSaved": "Configurações do modelo salvas para {model}", + "models.settingsSaveFailed": "Não foi possível confirmar se as configurações foram salvas. Recarregue a lista de modelos antes de tentar novamente.", + "models.settingsRefreshFailed": "Salvo. A lista de modelos não foi recarregada, então a linha pode mostrar valores antigos. Recarregue para conferir.", + "models.settingsSavedCodexStale": "{model} salvo, mas o catálogo de modelos do Codex não foi atualizado. Clique em Sincronizar agora no Painel para o Codex receber a alteração.", + "models.settingsRejected": "O servidor rejeitou estas configurações, então nada foi salvo. Confira os valores e tente novamente.", + "models.settingsDefaultEffort": "Nível padrão", + "models.settingsModalitiesInherit": "Sem substituição — seguindo atualmente a declaração do upstream: {modalities}", + "models.settingsModalitiesUnknown": "não declarado", + "models.settingsRestore": "Restaurar", + "models.settingsRestoreHint": "Limpa todas as substituições deste modelo e volta aos valores calculados.", + "models.settingsRestoreConfirm": "Restaurar {model} para os valores calculados? Isso limpa as substituições de janela de contexto, modalidade e raciocínio.", + "models.settingsRestored": "{model} restaurado para os valores calculados", + "models.settingsNothingToRestore": "{model} não tem substituições; já está nos valores calculados", + "pws.manageModelVisibility": "Gerenciar visibilidade em Modelos", + "pws.modelOwnershipLoading": "Verificando as definições de modelos…", + "pws.modelOwnershipFailed": "Não foi possível carregar as definições de modelos. Tente novamente antes de fazer alterações.", + "pws.modelSelectionPending": "Conclua a seleção de modelos em Modelos antes de fazer alterações.", + "pws.modelSaved": "Definição de modelo personalizado salva.", + "pws.modelSavedHidden": "Definição salva. Este modelo está oculto; gerencie a visibilidade em Modelos.", + "pws.modelSavedRefreshPending": "Definição salva, mas não foi possível atualizar o catálogo de modelos. Tente a atualização novamente; não o adicione de novo.", + "pws.modelMutationUnconfirmed": "Não foi possível confirmar a alteração. Atualize os modelos antes de tentar novamente.", + "pws.modelRemovedRefreshPending": "A alteração foi salva, mas não foi possível atualizar o catálogo de modelos. Tente a atualização novamente.", + "pws.modelHidden": "Modelo oculto. Restaure a visibilidade em Modelos.", + "pws.modelDefinitionDeleted": "Definição personalizada excluída. Um modelo subjacente ainda pode aparecer.", + "pws.modelKnown": "Este modelo já é conhecido. Gerencie a visibilidade dele em Modelos.", + "models.customAdded": "Modelo personalizado adicionado", + "models.customUpdated": "Modelo personalizado atualizado", + "models.customDeleted": "Modelo personalizado excluído", + "models.customSaveFailed": "Falha ao salvar o modelo personalizado", + "models.customSaving": "Salvando…", + "models.customAddBtn": "Adicionar", + "models.customEditBtn": "Atualizar", + "models.customEdit": "Editar", + "models.customDelete": "Excluir", + "models.customDeleteConfirm": "Excluir a definição personalizada de {name}? Um modelo nativo ou descoberto subjacente pode voltar a aparecer.", + "models.hide": "Ocultar", + "models.hideConfirm": "Ocultar {name} do catálogo de modelos? Isso não remove a definição nem altera a política de roteamento direto.", + "models.customBadge": "Personalizado", + "models.customSummary": "{count} personalizado(s)", + "models.customFieldModelId": "ID do modelo (slug do endpoint)", + "models.customFieldModelIdPlaceholder": "ex.: qwen4-max-preview", + "models.customFieldDisplayName": "Nome de exibição (opcional)", + "models.customFieldDisplayNamePlaceholder": "ex.: Qwen 4 Max Preview", + "models.customFieldContext": "Janela de contexto", + "models.customFieldModalities": "Modalidades de entrada", + "models.customFieldReasoning": "Esforço de raciocínio", + "models.customFieldReasoningOverride": "Substituir o esforço de raciocínio", + "models.reasoningEffort.none": "Nenhum", + "models.reasoningEffort.minimal": "Mínimo", + "models.reasoningEffort.low": "Baixo", + "models.reasoningEffort.medium": "Médio", + "models.reasoningEffort.high": "Alto", + "models.reasoningEffort.xhigh": "Extra alto", + "models.reasoningEffort.max": "Máximo", + "models.reasoningEffort.ultra": "Ultra", + "models.tipProvider": "Provedor", + "models.tipContext": "Contexto", + "models.tipModalities": "Modalidades", + "models.tipStatus": "Status", + "models.tipActive": "Ativo", + "models.tipDisabled": "Desativado", + "models.applied": "Salvo. O Codex mostra a alteração depois de recarregar a lista de modelos.", + "models.integrationRefreshWarning": "Seleção de modelos salva. Alguns catálogos de clientes não puderam ser atualizados. Verifique Integrações antes de iniciar uma nova sessão.", + "models.saveFailed": "Falha ao salvar", + "models.networkError": "Erro de rede — o proxy está em execução?", + "models.loadFail": "Falha ao carregar os modelos — o proxy está em execução?", + "models.noRouted": "Nenhum modelo roteado", + "models.noRoutedHint": "Faça login em um provedor ou adicione um primeiro.", + "models.emptyDiscovery": "Nenhum modelo foi descoberto. Verifique o endpoint do provedor ou adicione um modelo estático/personalizado.", + "models.emptyDiscoveryDisabled": "A descoberta de modelos em tempo real está desativada e nenhum modelo estático está configurado.", + "models.discoveryFailedBadge": "Falha na descoberta", + "models.discoveryFailedHttp": "A descoberta de modelos falhou (HTTP {status}).", + "models.discoveryFailedBlocked": "A descoberta de modelos foi bloqueada pela política do destino.", + "models.discoveryFailedInvalidResponse": "A descoberta de modelos retornou uma resposta inválida.", + "models.discoveryFailedNetwork": "A descoberta de modelos falhou por erro de rede.", + "models.discoveryFailedProvider": "O provedor informou um erro na descoberta de modelos.", + "models.discoveryFailedGeneric": "A descoberta de modelos falhou.", + "models.openProviderSettings": "Abrir configurações do provedor", + "models.inactiveNoCredit": "Sem crédito", + "models.inactiveNoCreditHint": "Todos os provedores por trás desta entrada estão sem crédito no momento, então uma requisição falharia. Ela continua listada e volta a ser utilizável quando o crédito for renovado ou recarregado.", + "models.discoveryFailedDependency": "A descoberta de modelos está ativada para este provedor. Enquanto continuar falhando, desative “{control}” nas configurações do provedor para usar modelos adicionados manualmente ou estáticos.", + "models.loading": "Carregando…", + "models.search": "Buscar modelos…", + "models.showMore": "Mostrar mais {n}", + "models.freeOnly": "Somente gratuitos", + "models.noFreeMatch": "Nenhum modelo gratuito aqui. Desative “Somente gratuitos” para ver os demais.", + "models.allowlistLabel": "Somente selecionados", + "models.allowlistHint": "Somente os modelos marcados vão para o catálogo (vazio = todos). Útil para provedores que expõem milhares de modelos.", + "models.selectedCount": "{n} selecionados", + "sub.forceTitle": "Forçar todos os subagentes a usar o mesmo modelo", + "sub.forceModel": "Modelo dos subagentes do Claude Code", + "sub.forceChoose": "Selecione um modelo exposto", + "sub.forceHelp": "Aplica-se aos agentes de plugins e aos agentes integrados (incluindo Explore/Plan), substituindo os modelos definidos por chamada. Forks e skills com model: inherit mantêm o modelo da conversa principal. O modelo principal e os modelos auxiliares Haiku/small-fast não são afetados.", + "sub.forceScope": "Desativado por padrão. Aplica-se a partir da próxima execução com roteamento via ocx claude, não à execução direta de claude. As variáveis exportadas no shell têm prioridade; env em settings.json pode sobrescrevê-las. O status verifica apenas a CLI e as configurações do usuário no servidor, não as de outro shell nem as do projeto.", + "sub.forceInvalid": "O destino configurado não está disponível. A substituição do modelo na inicialização será ignorada; selecione um modelo exposto ou desative esta opção.", + "sub.forceOld": "A versão do Claude Code é anterior à 2.1.257: FORCE é ignorado; aplica-se apenas o modelo padrão dos subagentes, sem imposição.", + "sub.forceUnknown": "A versão do Claude Code é desconhecida. Forçar um modelo requer a versão 2.1.257 ou posterior.", + "sub.forceOverride": "Sobrescrito por settings.json: seu env contém um modelo de subagente ou uma configuração FORCE. O OpenCodex não modifica esse arquivo.", + "sub.forceSettingsUnknown": "Não foi possível inspecionar settings.json. Não se sabe se suas configurações sobrescrevem esta opção.", + "sub.forceSaved": "Salvo. Entra em vigor na próxima execução com roteamento via ocx claude.", + "sub.subtitle": "O {cmd} do Codex anuncia apenas os 5 primeiros modelos (por prioridade) como substituições. Escolha até 5 aqui — gpt nativo ou roteado — e o opencodex define a prioridade deles no catálogo para que exatamente esses fiquem à frente. Qualquer outro modelo continua acessível pelo nome exato; isto controla apenas o que é exibido.", + "sub.featured": "Em destaque", + "sub.advanced": "Avançado", + "sub.orderHintAria": "Como esta ordem é usada", + "sub.orderHint": "A ordem exibida aqui define as posições 1 a 5 no topo do seletor de modelos do Codex e os modelos padrão candidatos para {cmd}.", + "sub.noneSelected": "Nenhum selecionado — escolha na lista abaixo.", + "sub.models": "Modelos", + "sub.search": "Buscar modelos (gpt nativo + roteados)…", + "sub.settings": "Configurações", + "sub.sections": "Seções de subagentes", + "sub.delegation.model": "Modelo a chamar primeiro", + "sub.delegation.modelHint": "O modelo que o Codex aciona primeiro ao delegar trabalho. “Em destaque” acima é a lista que ele pode chamar; este é o que ele chama primeiro.", + "sub.suggest.button": "Sugerir", + "sub.suggest.label": "Trabalho delegado", + "sub.suggest.placeholder": "O que o Codex costuma delegar? Ex.: buscas somente leitura em todo o repositório ou refatorações em vários módulos", + "sub.suggest.submit": "Dimensionar", + "sub.suggest.running": "Dimensionando…", + "sub.suggest.failed": "Não foi possível dimensionar o trabalho delegado.", + "sub.suggest.title": "Modelo sugerido", + "sub.suggest.accept": "Usar este", + "sub.noModels": "Nenhum modelo — faça login em um provedor ou adicione um primeiro.", + "sub.saved": "{n} modelos salvos. Inicie uma nova sessão do Codex (ou execute {cmd}) para vê-los como substituições de spawn_agent.", + "sub.saveFailed": "Falha ao salvar", + "sub.networkError": "Erro de rede — o proxy está em execução?", + "sub.loadFail": "Falha ao carregar os modelos — o proxy está em execução?", + "sub.loading": "Carregando…", + "sub.moveUp": "Mover {m} para cima", + "sub.moveDown": "Mover {m} para baixo", + "sub.removeAria": "Remover {m}", + "sub.workspace.addToFeatured": "Adicionar {m} aos destaques", + "sub.workspace.allModels": "Todos os modelos", + "sub.workspace.featuredFull": "A lista de destaques está cheia (máx. 5)", + "sub.workspace.mainAria": "Detalhes do modelo de subagente", + "sub.workspace.notFeatured": "Fora dos destaques", + "sub.workspace.priority": "Prioridade", + "sub.ultraMode": "Delegação sempre proativa", + "sub.ultraModeHint": "Ativa a política de delegação multiagente proativa para todos os modelos e esforços de raciocínio (não altera o esforço de raciocínio em si). Grava features.multi_agent_v2.multi_agent_mode_hint_text no config.toml.", + "sub.ultraModeV2Required": "Requer a superfície multiagente v2 — ative multi_agent_v2 e selecione v2 no controle de modo de subagente primeiro.", + "sub.ultraModeText": "Texto da delegação proativa", + "sub.ultraModePreset": "Restaurar predefinição", + "sub.ultraModeLoadFail": "Falha ao carregar as configurações de delegação proativa — o proxy está em execução?", + "sub.ultraModeSaveFail": "Falha ao salvar as configurações de delegação proativa", + "sub.ultraModeSaved": "Delegação proativa salva. Vale para novas sessões do Codex.", + "sub.workspace.removeFromFeatured": "Remover {m} dos destaques", + "sub.workspace.selectModel": "Selecione um modelo", + "sub.workspace.selectModelDesc": "Escolha um modelo na lista para ver os detalhes e colocá-lo em destaque para o spawn_agent.", + "sub.workspace.selector": "Seletor público", + "sub.fallbackLabel": "Cadeia de fallback de subagente", + "sub.fallbackHint": "Modelos tentados, em ordem, quando o modelo do subagente estiver indisponível ou falhar.", + "sub.fallbackAdd": "Adicionar modelo de fallback…", + "sub.fallbackPoll": "Intervalo de verificação de disponibilidade", + "sub.fallbackSaved": "Configurações de fallback de subagente salvas.", + "sub.fallbackSaveFailed": "Falha ao salvar as configurações de fallback", + "sub.fallbackUnavailable": "Não anunciado no momento; mantido na cadeia.", + "sub.fallbackPollInvalid": "Informe um número inteiro de 5000 a 600000 ms.", + "sub.v2Compatibility.title": "Compatibilidade V2 com pai nativo", + "sub.v2Compatibility.risk": "Se um pai nativo do ChatGPT delegar a este modelo roteado usando V2, a tarefa pode ser criptografada e falhar antes da execução. Tarefas legíveis vindas de pais roteados não são afetadas.", + "sub.v2Compatibility.recoveryUnknown": "O estado de recuperação ativada/elegibilidade não é exposto por este servidor. Use delegação V1/compatível com texto simples, ou ative a recuperação V2 experimental somente se elegível. A recuperação adiciona consumo de cota, latência, dependência do backend e possível perda de fidelidade; ela não corrige o protocolo upstream.", + "sub.v2Compatibility.details": "Detalhes de compatibilidade", + "logs.title": "Logs de requisições", + "logs.tabLogs": "Logs", + "logs.tabDebug": "Debug", + "logs.subtitle": "Requisições recentes roteadas pelo proxy local do opencodex, das mais novas para as mais antigas.", + "logs.autoRefresh": "Atualização automática", + "logs.noRequests": "Nenhuma requisição ainda.", + "logs.loadError": "Não foi possível carregar os logs de requisições.", + "logs.filter.surface.label": "Superfície", + "logs.filter.model.all": "Todos os modelos", + "logs.filter.provider.label": "Provedor", + "logs.filter.provider.all": "Todos os provedores", + "logs.filter.status.label": "Status", + "logs.filter.status.all": "Todos os status", + "logs.filter.status.success": "Sucesso (2xx)", + "logs.filter.status.errors": "Erros (4xx/5xx)", + "logs.filter.time.label": "Período", + "logs.filter.time.all": "Todo o período", + "logs.filter.time.15m": "Últimos 15 min", + "logs.filter.time.1h": "Última 1 h", + "logs.filter.time.24h": "Último 1 dia", + "logs.filter.speed.label": "Velocidade", + "logs.filter.speed.all": "Todas as velocidades", + "logs.filter.speed.slow": "< 15 tok/s", + "logs.filter.speed.medium": "15–< 50 tok/s", + "logs.filter.speed.fast": "≥ 50 tok/s", + "logs.filter.reset": "Limpar filtros", + "logs.filter.showingCount": "Exibindo {count} de {total}", + "logs.noMatchingRequests": "Nenhuma requisição correspondente.", + "logs.filter.surface.all": "Todas", + "logs.filter.surface.claude": "Claude", + "logs.filter.surface.codex": "Codex", + "logs.filter.surface.grok": "Grok", + "logs.filter.interceptedHelpersOnly": "Somente auxiliares interceptados", + "logs.badge.interceptedHelper": "I · {model}", + "logs.badge.interceptedHelperTitle": "Requisição de auxiliar interceptada", + "logs.filter.conversation.label": "Conversa", + "logs.filter.conversation.placeholder": "Cole o ID da conversa", + "logs.filter.conversation.clear": "Limpar", + "logs.filter.model.label": "Modelo", + "logs.filter.model.placeholder": "Filtrar por modelo ou provedor", + "logs.filter.conversation.apply": "Filtrar logs", + "logs.conversation.totals": "{requests} requisições · {tokens} tokens · {cost}", + "logs.conversation.scope": "Os totais cobrem apenas o buffer de logs atualmente carregado.", + "logs.conversation.excluded": "({unpriced} sem preço, {unmetered} sem medição excluídas do ~$)", + "logs.cost.approximate": "{amount}", + "logs.cost.lowerBound": "≥{amount}", + "logs.cost.unavailable": "—", + "logs.detail.conversation": "Conversa", + "logs.badge.claude": "Claude", + "logs.badge.grok": "Grok", + "logs.col.time": "Hora", + "logs.col.request": "Requisição", + "logs.col.model": "Modelo", + "logs.col.effort": "Esforço", + "logs.col.provider": "Provedor", + "logs.col.status": "Status", + "logs.col.tokens": "Tokens", + "logs.col.tokPerSec": "tok/s", + "logs.col.estimatedCost": "~$", + "logs.metric.tokPerSecTitle": "Tokens de saída por segundo durante toda a duração da requisição", + "logs.metric.estimatedCostTitle": "Equivalente ao preço de tabela da API, não uma cobrança real; preços sem correspondência ficam indisponíveis", + "usage.cost.total": "Equivalente ao preço de tabela da API (este período)", + "usage.cost.disclaimer": "Não é um comprovante de cobrança. Pode valer o uso da assinatura ou créditos do provedor.", + "usage.cost.unpricedNote": "{count} requisições excluídas (sem preço ou uso)", + "usage.cost.excluded": "({count} requisições excluídas)", + "usage.cost.excludedOne": "({count} requisição excluída)", + "logs.detail.section.basic": "Informações básicas", + "logs.detail.route.section": "Decisão de rota", + "logs.detail.route.kind": "Tipo de rota", + "logs.detail.route.profile": "Perfil", + "logs.detail.route.selected": "Selecionado", + "logs.detail.route.candidates": "Candidatos", + "logs.detail.route.unknown": "Nenhum rastreio de rota registrado para esta requisição (linha anterior ao rastreio).", + "logs.filter.protocol.label": "Caminho do protocolo", + "logs.filter.protocol.all": "Todos os caminhos", + "logs.filter.protocol.none": "Sem dados de caminho", + "logs.protocol.mode.native": "Nativo", + "logs.protocol.mode.translated": "Traduzido", + "logs.protocol.mode.legacyBridge": "Ponte legada", + "logs.protocol.mode.blocked": "Bloqueado", + "logs.protocol.disposition.passthrough": "Repassado", + "logs.protocol.disposition.translated": "Traduzido", + "logs.protocol.disposition.degraded": "Degradado", + "logs.protocol.disposition.unsupported": "Descartado", + "logs.protocol.wire.responses": "Responses", + "logs.protocol.wire.chat": "Chat", + "logs.protocol.wire.messages": "Messages", + "logs.protocol.wire.other": "Outro adaptador", + "logs.protocol.hop.ir": "IR", + "logs.protocol.hop.internal": "Responses (interno)", + "logs.protocol.badgeTitle": "Caminho do protocolo: {path} ({mode})", + "logs.detail.protocol.section": "Caminho do protocolo", + "logs.detail.protocol.inbound": "API do cliente", + "logs.detail.protocol.mode": "Modo de entrega", + "logs.detail.protocol.upstream": "Wire upstream", + "logs.detail.protocol.requestPath": "Caminho da requisição", + "logs.detail.protocol.responsePath": "Caminho da resposta", + "logs.detail.protocol.reasons": "Motivos", + "logs.detail.protocol.features": "Efeitos nos recursos", + "logs.detail.protocol.attempts": "Caminhos das tentativas", + "logs.detail.protocol.attempt": "Tentativa {ordinal}", + "logs.detail.protocol.none": "Nenhum dado de caminho registrado para esta requisição.", + "logs.detail.section.performance": "Desempenho", + "logs.detail.section.cost": "Equivalente ao preço de tabela da API", + "logs.detail.section.attempts": "Tentativas do combo", + "logs.detail.section.usage": "Uso bruto", + "logs.detail.ttft": "TTFT", + "logs.detail.decodeTokPerSec": "Taxa de decodificação (est.)", + "logs.detail.reason.ttft_missing": "Nenhum tempo até o primeiro token foi registrado para esta requisição, portanto não há janela de decodificação para medir.", + "logs.detail.reason.decode_window_too_short": "A janela de saída medida foi inferior a um segundo, curta demais para estimar a taxa de decodificação.", + "logs.detail.costTotal": "Equivalente ao preço de tabela", + "logs.detail.totalTokens": "Total de tokens", + "logs.detail.matchedKey": "Chave de preço correspondente", + "logs.detail.priceSource": "Fonte do preço", + "logs.detail.unavailableReason": "Motivo da indisponibilidade", + "logs.detail.copyRequestId": "Copiar ID da requisição", + "logs.detail.copied": "Copiado", + "logs.detail.source.jawcode": "catálogo jawcode", + "logs.detail.source.expected": "Sobreposição de preço esperado", + "logs.detail.source.user": "Sobreposição de preço configurada no provedor", + "logs.detail.verification.verified": "Verificado", + "logs.detail.verification.derived": "Derivado do modelo base", + "logs.detail.attempt.target": "Provedor / modelo", + "logs.detail.attempt.reason": "Resultado / motivo", + "logs.detail.attempt.completed": "Concluída", + "logs.detail.attempt.e2eNote": "O tok/s de nível superior é ponta a ponta; cada tentativa usa sua própria duração.", + "logs.detail.attempt.recovery.transient5xx": "5xx transitório", + "logs.detail.attempt.recovery.connectionReset": "Conexão reiniciada", + "logs.detail.attempt.recovery.oauth401": "Reautenticação OAuth", + "logs.detail.attempt.recovery.key429": "Chave com limite de taxa (429)", + "logs.detail.attempt.recovery.rateLimit429": "Limite de taxa (429)", + "logs.detail.attempt.recovery.anthropicOauth429": "OAuth da Anthropic com limite de taxa (429)", + "logs.detail.attempt.recovery.image413": "Payload de imagem grande demais (413)", + "logs.detail.attempt.recovery.emptyCompletion": "Nova tentativa por conclusão vazia", + "logs.detail.attempt.recovery.consoleGoUpload": "Nova tentativa de upload no console", + "logs.detail.attempt.recovery.key401": "Reautenticação da chave de API", + "logs.detail.attempt.recovery.oauthAccount403": "Validação da conta necessária (403)", + "logs.detail.attempt.recovery.oauthAccount429": "Conta com limite de taxa (429)", + "logs.detail.attempt.recovery.opaqueBlobRejection": "Estado criptografado obsoleto descartado", + "logs.detail.attempt.recovery.reasoningEffortDowngrade": "Esforço de raciocínio reduzido", + "logs.detail.attempt.recovery.anthropicFastDowngrade": "Modo rápido recusado, repetido em velocidade padrão", + "logs.detail.outcome.label": "Resultado", + "logs.detail.outcome.completed": "Concluída", + "logs.detail.outcome.failed": "Falhou", + "logs.detail.outcome.incomplete": "Incompleta", + "logs.detail.outcome.aborted": "Abortada", + "logs.detail.sends.label": "Envios upstream", + "logs.detail.sends.unresolved": "não resolvido", + "logs.detail.attempt.recovery.unknown": "Motivo de recuperação desconhecido", + "logs.detail.cause.label": "Causa da falha", + "logs.detail.stage.label": "chegou a", + "logs.detail.resend.label": "Reenvio", + "logs.detail.cause.transportUnsent": "Nunca enviada ao upstream", + "logs.detail.cause.transportAmbiguous": "Conexão perdida, estado do upstream desconhecido", + "logs.detail.cause.upstreamDeclined": "O upstream recusou iniciar", + "logs.detail.cause.rateLimit": "Limite de taxa atingido", + "logs.detail.cause.quotaExhausted": "Cota esgotada", + "logs.detail.cause.credentialRejected": "Credenciais rejeitadas", + "logs.detail.cause.policyRefusal": "Recusada pela política de conteúdo", + "logs.detail.cause.parameterRejected": "Parâmetro da requisição rejeitado", + "logs.detail.cause.ciphertextRefusal": "Estado criptografado rejeitado", + "logs.detail.cause.payloadTooLarge": "Payload grande demais", + "logs.detail.cause.payloadRejected": "Payload rejeitado", + "logs.detail.cause.upstreamFault": "Falha do upstream", + "logs.detail.cause.emptyOutput": "Nenhuma saída utilizável", + "logs.detail.cause.clientCancelled": "Cancelada pelo cliente", + "logs.detail.cause.localRefusal": "Recusada por este proxy", + "logs.detail.stage.preHeader": "sem cabeçalho de resposta", + "logs.detail.stage.headersOnly": "somente cabeçalhos", + "logs.detail.stage.protocolPrelude": "preâmbulo do protocolo", + "logs.detail.stage.semanticOutput": "saída entregue", + "logs.detail.stage.sideEffect": "efeito colateral emitido", + "logs.detail.stage.terminal": "resposta entregue", + "logs.detail.resend.permitted": "Poderia ser reenviada", + "logs.detail.resend.permittedAfterRepair": "Poderia ser reenviada após reparo", + "logs.detail.resend.refusedAmbiguous": "Não reenviada: estado do upstream desconhecido", + "logs.detail.resend.refusedCommitted": "Não reenviada: o chamador já viu a saída", + "logs.detail.resend.refusedFutile": "Não reenviada: a mesma requisição falharia de novo", + "logs.detail.reason.usage_missing": "O uso não foi informado.", + "logs.detail.reason.usage_unsupported": "Este provedor não informa o uso.", + "logs.detail.reason.output_missing": "Nenhuma contagem positiva de tokens de saída foi informada.", + "logs.detail.reason.invalid_duration": "A duração da requisição não é válida.", + "logs.detail.reason.price_unmatched": "Nenhum preço correspondente foi encontrado.", + "logs.detail.reason.invalid_cache_breakdown": "Os detalhes de tokens de cache conflitam com o total de tokens de entrada.", + "logs.detail.reason.invalid_usage": "O uso contém um valor de token inválido.", + "logs.detail.reason.combo_attempt_unavailable": "Pelo menos uma tentativa do combo não pôde ser precificada.", + "logs.detail.estimate.usage_estimated": "O uso do provedor é estimado.", + "logs.detail.estimate.cache_detail_missing": "Os detalhes de cache estavam indisponíveis; a entrada é uma estimativa de limite superior.", + "logs.detail.estimate.expected_price_overlay": "Foi usado um preço de tabela esperado verificado.", + "logs.detail.estimate.provider_cost_overlay": "Foi usada uma sobreposição de preço configurada no provedor.", + "logs.detail.estimate.priority_lower_bound": "O preço Priority confirmado está indisponível; a estimativa exibida é um limite inferior conhecido.", + "logs.col.error": "Erro", + "logs.col.upstreamReason": "Motivo do upstream", + "logs.col.duration": "Duração", + "logs.modelTooltip.model": "modelo", + "logs.modelTooltip.resolvedModel": "modelo resolvido", + "logs.modelTooltip.servedModel": "modelo atendido", + "logs.modelTooltip.wireModel": "modelo no wire", + "logs.modelRerouteTitle": "O upstream atendeu um modelo diferente do enviado", + "logs.modelTooltip.requestedTier": "nível solicitado", + "logs.modelTooltip.configuredTier": "nível configurado", + "logs.modelTooltip.responseTier": "nível da resposta", + "logs.modelTooltip.supportsTier": "suporte a nível", + "logs.modelTooltip.tierOutcome.confirmed": "confirmado", + "logs.modelTooltip.tierOutcome.assumed": "presumido", + "logs.modelTooltip.tierOutcome.downgraded": "rebaixado", + "logs.modelTooltip.tierOutcome.unknown": "desconhecido", + "logs.tokens.reported": "informado", + "logs.tokens.unreported": "não informado", + "logs.tokens.unsupported": "não suportado", + "logs.tokens.estimated": "estimado", + "logs.tokens.input": "entrada", + "logs.tokens.output": "saída", + "logs.tokens.cacheRead": "leitura de cache (c)", + "logs.tokens.cacheWrite": "gravação de cache (w)", + "logs.tokens.reasoning": "raciocínio", + "logs.tokens.noCache": "sem dados de cache", + "logs.tokens.contextTotal": "contexto ativo", + "logs.tokens.noCacheNote": "este provedor não informa tokens de cache", + "logs.tokens.noCacheCursor": "Detalhe de cache do Cursor não informado", + "logs.tokens.noCacheCursorNote": "O Cursor não expõe as contagens de tokens de leitura/gravação de cache; isso é desconhecido, não um cache miss confirmado", + "logs.tokens.estimatedNote": "estimado (o provedor não informa o uso exato)", + "logs.details": "Detalhes", + "logs.detailTitle": "Detalhes da requisição", + "logs.detailRaw": "Entrada de log bruta", + "debug.title": "Debug", + "debug.subtitle": "Diagnósticos opcionais de transporte do provedor e de extração de uso. Erros de requisição e 502 permanecem na aba Logs.", + "debug.debug": "Debug do provedor", + "debug.usage": "Extração de uso", + "debug.injection": "Log de injeção", + "debug.claude": "Entrada do Claude", + "debug.claudeInbound.title": "Requisições de entrada do Claude", + "debug.claudeInbound.sub": "O que o Claude Code/Desktop realmente envia (thinking, esforço, metadados) — nenhum texto de prompt é armazenado.", + "debug.claudeInbound.empty": "Nenhuma requisição capturada ainda. Envie uma mensagem pelo Claude enquanto isto estiver ativado.", + "debug.claudeInbound.time": "Hora", + "debug.claudeInbound.endpoint": "Endpoint", + "debug.claudeInbound.model": "Modelo", + "debug.claudeInbound.none": "nenhum", + "debug.reset": "Limpar substituições em tempo de execução", + "debug.refresh": "Atualizar", + "debug.follow": "Acompanhar", + "debug.streamProvider": "Provedor", + "debug.streamUsage": "Uso", + "debug.streamInjection": "Injeção", + "debug.loading": "Carregando configurações de debug…", + "debug.loadFailed": "Não foi possível carregar as configurações de debug.", + "debug.emptyTitle": "O log de debug está desativado", + "debug.empty": "Ative Debug do provedor ou Extração de uso no cartão acima. As linhas aparecem aqui depois que você enviar uma requisição pelo proxy.", + "debug.noLinesTitle": "Aguardando linhas", + "debug.noLines.provider": "O debug do provedor está ativado, mas registra apenas anomalias de transporte (frames descartados ou malformados e eventos de conexão/nova tentativa do Cursor). Uma requisição normal por um provedor como a Anthropic pode não gerar nenhuma linha.", + "debug.noLines.usage": "A extração de uso está ativada, mas nada foi capturado ainda. Envie um chat/requisição pelo Codex e ele aparecerá aqui.", + "debug.noLines.injection": "O log de injeção está ativado, mas nada foi capturado ainda. Ele registra a injeção de orientação multiagente e as decisões de limite de esforço em turnos de colaboração e de subagentes.", + "usage.title": "Uso", + "usage.subtitle": "Contabilização local de tokens do seu proxy. O uso ausente nunca é exibido como zero.", + "usage.loading": "Carregando dados de uso…", + "usage.empty": "Nenhum uso registrado ainda. Envie uma requisição pelo proxy para ver a atividade aqui.", + "usage.loadError": "Não foi possível carregar os dados de uso.", + "usage.range.all": "Tudo", + "usage.range.available": "Histórico disponível", + "usage.historyTruncated": "Os totais cobrem apenas o histórico disponível porque o uso mais antigo não foi carregado.", + "usage.historyTruncatedWindow": "As linhas carregadas têm horários de início de requisição entre {start} e {end}. Entradas anteriores do arquivo foram omitidas pelo limite de leitura, então qualquer período selecionado pode estar incompleto.", + "usage.range.30d": "30d", + "usage.range.7d": "7d", + "usage.card.requests": "Requisições", + "usage.card.measured": "Medidas", + "usage.card.reported": "Informadas", + "usage.card.totalTokens": "Total de tokens", + "usage.card.cachedTokens": "Leituras de cache", + "usage.card.cachedTokensHint": "Tokens do prompt atendidos pelo cache do provedor (leituras). As gravações de cache aparecem abaixo quando houver.", + "usage.card.cacheWriteTokens": "gravações de cache", + "usage.card.coverage": "Cobertura", + "usage.card.activeDays": "Dias ativos", + "usage.section.heatmap": "Atividade diária", + "usage.section.overview": "Visão geral", + "usage.section.models": "Modelos", + "usage.section.providers": "Provedores", + "usage.section.coverage": "Detalhamento da cobertura", + "usage.section.companion": "Barra de menus e widget", + "usage.companion.title": "Barra de menus e widget", + "usage.companion.description": "As configurações aqui controlam o app desktop/de barra de menus do OpenCodex e seu widget.", + "usage.companion.installGuide": "Guia de instalação", + "usage.companion.loading": "Carregando linha do tempo…", + "usage.companion.timelineUnavailable": "Linha do tempo indisponível", + "usage.companion.empty": "Nenhum uso nas últimas {hours} h", + "usage.companion.chartLabel": "Linha do tempo de uso", + "usage.companion.olderRecordsSkipped": "Registros mais antigos foram ignorados", + "usage.companion.settingsUnavailable": "Configurações do companion indisponíveis", + "usage.companion.corrupt": "O arquivo de configurações do companion está corrompido. Os controles mostram os padrões; o salvamento fica pausado até você substituir o arquivo.", + "usage.companion.corruptReset": "Substituir pelos padrões", + "usage.companion.connected": "App da barra de menus conectado · {age}", + "usage.companion.connectedDesktop": "App desktop conectado · {age}", + "usage.companion.installTitle": "Instalar o app desktop", + "usage.companion.installOs": "Sistema operacional", + "usage.companion.osMac": "macOS", + "usage.companion.osWindows": "Windows", + "usage.companion.osLinux": "Linux", + "usage.companion.runningInDesktop": "Você está no app desktop do OpenCodex {version}", + "usage.companion.openInBrowser": "Abrir no navegador", + "usage.companion.installMacStep1": "Baixe OpenCodex--macos.dmg da versão mais recente e arraste OpenCodex.app para Aplicativos.", + "usage.companion.installMacStep2": "Primeira execução: clique com o botão direito em OpenCodex.app → Abrir (o Gatekeeper pergunta uma vez até o app ser notarizado).", + "usage.companion.installMacStep3": "O app encontra este proxy sozinho; o widget aparece na galeria de widgets depois que o app for executado.", + "usage.companion.installWinStep1": "Baixe OpenCodex--windows-x64.msi da versão mais recente e execute-o.", + "usage.companion.installWinStep2": "Se o SmartScreen avisar, escolha Mais informações → Executar assim mesmo (o instalador ainda não tem assinatura de código).", + "usage.companion.installWinStep3": "O OpenCodex aparece na bandeja do sistema e se conecta a este proxy, ou inicia o proxy embutido.", + "usage.companion.installLinuxStep1": "Baixe OpenCodex--linux-x86_64.AppImage (ou o .deb) da versão mais recente.", + "usage.companion.installLinuxStep2": "Use chmod +x no AppImage e execute-o; o ícone na bandeja exige um desktop compatível com AppIndicator.", + "usage.companion.installLinuxStep3": "O app se conecta a este proxy ou inicia o proxy embutido.", + "usage.companion.notConnected": "Nenhum app da barra de menus se conectou a este proxy ainda.", + "usage.companion.lastSeen": "Visto pela última vez {age}", + "usage.companion.installAnother": "Instalar em outro dispositivo", + "usage.companion.saved": "Salvo · {time}", + "usage.companion.saveFailed": "Não foi possível salvar: {error}", + "usage.companion.reset": "Restaurar padrões", + "usage.companion.footer": "O widget mostra as requisições, os tokens e o custo de hoje, além do gráfico configurado aqui, e é atualizado quando o app consulta o proxy.", + "usage.companion.menuBarShows": "A barra de menus mostra", + "usage.companion.menuRequests": "Requisições", + "usage.companion.menuTokens": "Tokens", + "usage.companion.menuCost": "Custo", + "usage.companion.menuQuota": "Cota", + "usage.companion.menuNone": "Somente ícone", + "usage.companion.window": "Janela", + "usage.companion.window6": "6h", + "usage.companion.window24": "24h", + "usage.companion.window72": "3d", + "usage.companion.window168": "7d", + "usage.companion.style": "Estilo", + "usage.companion.styleLine": "Linha", + "usage.companion.styleStacked": "Empilhado", + "usage.companion.metric": "Métrica", + "usage.companion.metricTotal": "Total", + "usage.companion.metricInput": "Entrada", + "usage.companion.metricOutput": "Saída", + "usage.companion.metricCached": "Em cache", + "usage.companion.groupBy": "Agrupar por", + "usage.companion.groupModel": "Modelo", + "usage.companion.groupAccount": "Modelo + conta", + "usage.companion.popoverSections": "Seções do popover", + "usage.companion.sectionToday": "Hoje", + "usage.companion.sectionChart": "Gráfico", + "usage.companion.sectionModels": "Modelos", + "usage.companion.sectionCost": "Custo", + "usage.companion.sectionAccounts": "Contas", + "usage.companion.advanced": "Avançado", + "usage.companion.aggregation": "Agregação", + "usage.companion.aggregationSum": "Soma", + "usage.companion.aggregationAverage": "Média", + "usage.companion.aggregationMax": "Máx.", + "usage.companion.menuText": "Texto da barra de menus", + "usage.companion.placeholders": "Marcadores:", + "usage.companion.modelsOnChart": "Modelos no gráfico", + "usage.companion.modelsCount": "{selected} de {total} no gráfico", + "usage.companion.modelsShowAll": "Mostrar todos", + "usage.companion.hideProviders": "Ocultar provedores", + "usage.workspace.report": "Relatório de uso", + "usage.workspace.sections": "Seções de uso", + "usage.coverage.measured": "Medido", + "usage.coverage.reported": "Informado pelo provedor", + "usage.coverage.estimated": "Estimado", + "usage.coverage.note": "As entradas medidas incluem contagens de tokens informadas pelo provedor e estimadas. Requisições sem informação ou sem suporte são registradas, mas nunca contabilizadas como zero token.", + "usage.search.models": "Buscar modelos…", + "usage.col.requests": "Requisições", + "usage.col.measured": "Medido", + "usage.col.reported": "Informado", + "usage.col.inputTokens": "Tokens de entrada", + "usage.col.outputTokens": "Tokens de saída", + "usage.col.cacheHits": "Acertos de cache", + "usage.col.cacheWrites": "Gravações em cache", + "usage.col.cacheHitRate": "Taxa de acerto", + "usage.cacheHitRate.partial": "Média sobre {measured} de {total} tokens de entrada; as demais requisições não informaram detalhes de cache.", + "usage.cacheHitRate.unmeasured": "Nenhuma requisição desta linha informou detalhes de cache, então não há taxa de acerto para calcular a média.", + "usage.unavailable": "—", + "usage.col.tokens": "Tokens", + "usage.col.apiListPrice": "Preço de tabela da API", + "usage.col.share": "Participação", + "usage.heatmap.less": "Menos", + "usage.heatmap.more": "Mais", + "usage.dayMon": "Seg", + "usage.dayWed": "Qua", + "usage.dayFri": "Sex", + "usage.heatmap.tooltipTokens": "{tokens} tokens", + "usage.chart.dayDetail": "{date}: {requests} requisições, {tokens} tokens", + "usage.heatmap.keyboardLabel": "Use Cima e Baixo para mover por dia; Esquerda e Direita para mover por semana.", + "usage.heatmap.tooltipRequests": "{requests} requisições", + "nav.storage": "Armazenamento", + "storage.title": "Armazenamento", + "storage.subtitle": "Veja o que está ocupando o CODEX_HOME. A limpeza nunca afeta sessões ativas.", + "storage.loading": "Analisando o armazenamento…", + "storage.empty": "CODEX_HOME está vazio ou ausente — nada a relatar.", + "storage.error": "Falha na análise do armazenamento. Verifique se CODEX_HOME aponta para um diretório válido.", + "storage.refresh": "Reanalisar", + "storage.rescanned": "Análise concluída.", + "storage.card.total": "Tamanho total", + "storage.card.files": "Arquivos", + "storage.card.home": "CODEX_HOME", + "storage.snapshot.lastScan": "Última análise", + "storage.snapshot.scanning": "Analisando…", + "storage.snapshot.unavailable": "Nenhuma análise ainda.", + "storage.cleanupCard.title": "Liberar espaço", + "storage.cleanupCard.tabs": "Opções de limpeza", + "storage.cleanupCard.tab.policy": "Política", + "storage.cleanupCard.tab.quarantine": "Quarentena", + "storage.cleanup.noArchives": "Nenhuma sessão arquivada para limpar.", + "storage.section.buckets": "Categorias", + "storage.section.largest": "Maiores arquivos", + "storage.workspace.overview": "Visão geral", + "storage.workspace.selectBucket": "Selecione uma categoria na lista para ver o detalhamento.", + "storage.col.bucket": "Categoria", + "storage.col.size": "Tamanho", + "storage.col.files": "Arquivos", + "storage.col.oldest": "Mais antigo", + "storage.col.newest": "Mais recente", + "storage.col.rows": "Linhas do BD", + "storage.rows.unknown": "desconhecido (bloqueado)", + "storage.bucket.sessions": "Sessões ativas", + "storage.bucket.archived_sessions": "Sessões arquivadas", + "storage.bucket.logs_db": "Banco de logs", + "storage.bucket.state_db": "Banco de estado", + "storage.bucket.attachments": "Anexos", + "storage.bucket.deletion_manifests": "Manifestos de exclusão", + "storage.bucket.other": "Outros", + "storage.cleanup.title": "Limpeza de arquivados", + "storage.cleanup.help": "Remove as sessões arquivadas mais antigas por porcentagem. Sessões ativas nunca são afetadas. A quarentena é o padrão — os arquivos são movidos para CODEX_HOME/.trash.", + "storage.cleanup.slider": "Porcentagem dos arquivados mais antigos", + "storage.cleanup.percent": "{percent}%", + "storage.cleanup.preset": "{percent}", + "storage.cleanup.preview": "Pré-visualizar", + "storage.cleanup.confirmTitle": "Confirmar limpeza de arquivados", + "storage.cleanup.confirmBody": "Isto processará {count} arquivo(s) arquivado(s) (~{size}), os {percent}% mais antigos.", + "storage.cleanup.moreFiles": "…e mais {n}", + "storage.cleanup.permanent": "Excluir permanentemente (ignorar quarentena)", + "storage.cleanup.permanentWarn": "A exclusão permanente não pode ser desfeita.", + "storage.cleanup.quarantineNote": "Os arquivos são movidos para .trash em CODEX_HOME. Você pode restaurá-los na aba Quarentena.", + "storage.cleanup.cancel": "Cancelar", + "storage.cleanup.confirmQuarantine": "Colocar em quarentena", + "storage.cleanup.confirmPermanent": "Excluir permanentemente", + "storage.cleanup.doneQuarantine": "{count} arquivo(s) em quarentena ({size}).", + "storage.cleanup.donePermanent": "{count} arquivo(s) excluído(s) permanentemente ({size}).", + "storage.cleanup.skippedReferenced": "{count} arquivo(s) referenciado(s) ignorado(s).", + "storage.cleanup.previewFailed": "Falha na pré-visualização.", + "storage.cleanup.cleanupFailed": "Falha na limpeza.", + "storage.cleanup.err.codex_busy": "O Codex está usando o state.sqlite — tente novamente após encerrar o Codex.", + "storage.cleanup.err.stale_preview": "Os arquivos arquivados mudaram desde a pré-visualização — execute a pré-visualização novamente.", + "storage.cleanup.err.restore_pending_overlap": "Os arquivos selecionados se sobrepõem a uma restauração incompleta da lixeira — conclua ou tente novamente a restauração primeiro.", + "storage.cleanup.err.referenced_history": "Os arquivos selecionados ainda são referenciados por históricos bifurcados ou paginados.", + "storage.cleanup.err.invalid_digest": "O digest da pré-visualização está ausente ou é inválido.", + "storage.cleanup.err.invalid_mode": "O modo de limpeza deve ser quarentena ou permanente.", + "storage.cleanup.err.fs_failed": "Falha na limpeza do sistema de arquivos. Algumas alterações podem já ter sido aplicadas — verifique CODEX_HOME/.trash e qualquer caminho de recuperação exibido.", + "storage.cleanup.err.fs_failed_trash": "Falha na limpeza do sistema de arquivos. Algumas alterações podem já ter sido aplicadas — verifique {trashDir} e manifest.json para arquivos recuperáveis.", + "storage.cleanup.err.db_reconcile_failed": "Não foi possível atualizar o banco de estado do Codex.", + "storage.cleanup.err.cleanup_failed": "Falha na limpeza.", + "storage.trash.title": "Quarentena", + "storage.trash.help": "Sessões arquivadas movidas para CODEX_HOME/.trash. A restauração devolve os arquivos JSONL e as linhas de thread.", + "storage.trash.empty": "Nenhuma entrada em quarentena.", + "storage.trash.loading": "Carregando quarentena…", + "storage.trash.col.when": "Em quarentena desde", + "storage.trash.col.files": "Arquivos", + "storage.trash.col.size": "Tamanho", + "storage.trash.col.mode": "Modo", + "storage.trash.col.id": "Entrada", + "storage.trash.restore": "Restaurar", + "storage.trash.confirmTitle": "Restaurar entrada da quarentena?", + "storage.trash.confirmBody": "Restaurar {count} arquivo(s) (~{size}) de {id} para as sessões arquivadas.", + "storage.trash.cancel": "Cancelar", + "storage.trash.confirmRestore": "Restaurar", + "storage.trash.done": "{count} arquivo(s) restaurado(s) ({size}).", + "storage.trash.restoreFailed": "Falha na restauração.", + "storage.trash.listFailed": "Não foi possível listar as entradas da quarentena.", + "storage.trash.mode.quarantine": "quarentena", + "storage.trash.mode.permanent": "permanente (incompleto)", + "storage.trash.err.codex_busy": "O Codex está usando o state.sqlite — tente novamente após encerrar o Codex.", + "storage.trash.err.invalid_trash": "O ID da entrada da lixeira está ausente ou é inválido.", + "storage.trash.err.missing_trash": "A entrada da lixeira não foi encontrada.", + "storage.trash.err.dest_exists": "O destino da restauração já existe — remova ou renomeie o arquivo arquivado e tente novamente.", + "storage.trash.err.fs_failed": "Falha na restauração do sistema de arquivos. Alguns arquivos podem já ter sido restaurados — verifique archived_sessions e .trash.", + "storage.trash.err.db_reconcile_failed": "Não foi possível restaurar as linhas do banco de estado do Codex.", + "storage.trash.err.storage_mutation_busy": "Outra limpeza ou restauração de armazenamento está em andamento — tente novamente em instantes.", + "storage.trash.err.restore_failed": "Falha na restauração.", + "storage.trash.err.restore_worker_timeout": "A restauração demorou demais (mais de 10 minutos) e foi interrompida.", + "storage.trash.err.restore_worker_aborted": "A restauração foi cancelada durante o encerramento.", + "storage.trash.err.restore_worker_failed": "O worker de restauração travou ou falhou inesperadamente.", + "storage.policy.title": "Política de limpeza automática", + "storage.policy.help": "Limpeza em lote opcional quando as sessões arquivadas excedem um limite. Desativada por padrão — nunca é ativada automaticamente.", + "storage.policy.loading": "Carregando política…", + "storage.policy.loadFailed": "Não foi possível carregar a política de limpeza.", + "storage.policy.saveFailed": "Não foi possível salvar a política de limpeza.", + "storage.policy.runFailed": "Falha na execução da política.", + "storage.policy.alreadyRunning": "Já há uma execução da política de limpeza em andamento.", + "storage.policy.invalid": "Valores de política inválidos.", + "storage.policy.enabled": "Ativar limpeza automática", + "storage.policy.enabledHint": "O padrão é desativada. Quando ativada, executa apenas no agendamento escolhido (ou em Executar agora).", + "storage.policy.threshold": "Quando o tamanho dos arquivados exceder (GiB)", + "storage.policy.trigger": "Gatilho", + "storage.policy.target": "Alvo da limpeza", + "storage.policy.targetPercent": "Remover os arquivados mais antigos (%)", + "storage.policy.targetReduce": "Reduzir o tamanho dos arquivados para (GiB)", + "storage.policy.thresholdInc": "Aumentar limite", + "storage.policy.thresholdDec": "Diminuir limite", + "storage.policy.percentInc": "Aumentar porcentagem", + "storage.policy.percentDec": "Diminuir porcentagem", + "storage.policy.reduceInc": "Aumentar tamanho alvo", + "storage.policy.reduceDec": "Diminuir tamanho alvo", + "storage.policy.schedule": "Agendamento", + "storage.policy.schedule.manual": "Somente manual", + "storage.policy.schedule.startup": "Ao iniciar o proxy", + "storage.policy.schedule.daily": "Diário", + "storage.policy.schedule.weekly": "Semanal", + "storage.policy.mode": "Modo de exclusão", + "storage.policy.mode.quarantine": "Quarentena (padrão)", + "storage.policy.mode.permanent": "Exclusão permanente", + "storage.policy.permanentWarn": "O modo permanente não pode ser desfeito. Prefira a quarentena, a menos que tenha certeza.", + "storage.policy.lastRun": "Última execução", + "storage.policy.lastRunDetail": "Removidos {count} · liberados {size}", + "storage.policy.nextRun": "Próxima execução", + "storage.policy.never": "Nunca", + "storage.policy.save": "Salvar", + "storage.policy.runNow": "Executar agora", + "storage.policy.running": "Executando…", + "storage.policy.saved": "Política salva.", + "storage.policy.skippedDisabled": "A política está desativada — ative-a primeiro.", + "storage.policy.skippedUnder": "O tamanho dos arquivados está abaixo do limite — nada a fazer.", + "storage.policy.skippedEmpty": "Nenhum candidato arquivado correspondeu ao alvo.", + "storage.policy.doneQuarantine": "A política colocou {count} arquivo(s) em quarentena ({size}).", + "storage.policy.donePermanent": "A política excluiu permanentemente {count} arquivo(s) ({size}).", + "storage.policy.metadataSaveWarning": "A execução da política terminou, mas seus metadados de agendamento não puderam ser salvos.", + "modal.addNamed": "Adicionar: {label}", + "modal.add": "Adicionar provedor", + "modal.search": "Buscar provedores…", + "modal.logInWith": "Entrar com {label}", + "modal.waitingBrowser": "Aguardando o navegador…", + "modal.providerName": "Nome do provedor", + "modal.adapter": "Adaptador", + "modal.baseUrl": "URL base", + "modal.endpoint": "Endpoint", + "modal.endpoint.tokenPlan": "Plano de tokens", + "modal.endpoint.payAsYouGo": "Pague conforme o uso", + "modal.endpoint.custom": "Personalizado", + "modal.defaultModel": "Modelo padrão (opcional)", + "modal.allowPrivateNetwork": "Permitir rede local/privada", + "modal.allowPrivateNetworkHint": "Ative apenas para provedores hospedados por você de propósito. Os endpoints de metadados continuam bloqueados.", + "modal.nameRequired": "O nome do provedor é obrigatório", + "modal.baseUrlRequired": "A URL base é obrigatória", + "modal.networkError": "Erro de rede — o proxy está em execução?", + "modal.loginFailStart": "Falha ao iniciar o login", + "modal.waitingLogin": "Aguardando login no navegador…", + "modal.loggingIn": "Entrando…", + "modal.loginTimeout": "Tempo de login esgotado — tente novamente.", + "modal.back": "Voltar", + "modal.badge.oauth": "OAuth", + "modal.customProvider": "Provedor personalizado", + "modal.failedStatus": "Falhou ({status})", + "modal.loginError": "Erro de login: {error}", + "modal.badge.codexLogin": "Login do Codex", + "modal.badge.local": "Local", + "modal.badge.apiKey": "Chave de API", + "modal.badge.direct": "Direto", + "modal.badge.pool": "Pool", + "modal.badge.free": "Grátis", + "modal.badge.sponsor": "Patrocinador", + "modal.badge.subscriptionCli": "CLI por assinatura", + "pws.sponsor.orcaTitle": "Um modelo para cada prompt", + "pws.sponsor.orcaDescription": "Um gateway compatível com OpenAI com roteamento adaptativo e failover automático.", + "pws.sponsor.packyTitle": "Claude Code, Codex e Gemini em um só lugar", + "pws.sponsor.packyDescription": "Um relay de API para suas ferramentas de programação com IA. Comece com um token do grupo Codex.", + "pws.sponsor.tokenlabTitle": "Uma chave de API para os principais modelos", + "pws.sponsor.tokenlabDescription": "Responses, Chat Completions, streaming e chamada de ferramentas para agentes de programação, com modo de entrega à sua escolha e cobrança conforme o uso.", + "pws.sponsor.visit": "Conhecer {provider}", + "pws.sponsor.console": "Abrir console", + "modal.invalidPreset": "Este preset de provedor integrado está incompleto. Reinicie o proxy e tente novamente.", + "modal.freeTierTitle": "Plano gratuito", + "modal.subscriptionCliTitle": "CLI por assinatura, não chave de API", + "modal.subscriptionCliWarning": "Este provedor não usa chave de API. Ele aciona a CLI oficial do Claude Code autenticada nesta máquina ({cmd}) e cobra da conta de assinatura do Claude. O OpenCodex não armazena nenhuma chave para ele, e uma chave informada aqui nunca é usada. Para cobrança por chave de API, adicione {apiProvider}.", + "modal.freeTierDefault": "Não requer chave de API. Funciona de imediato.", + "modal.tab.accounts": "Contas", + "modal.tab.free": "Grátis", + "modal.tab.local": "Local", + "modal.tab.paid": "Pago", + "modal.noteMore": "Mostrar descrição completa", + "modal.searchResults": "{count} resultados em {tiers}", + "modal.accountsHint": "Entre aqui no ChatGPT/Codex, em provedores OAuth e em contas com chave de API. A OpenAI já está integrada — faça login em vez de adicioná-la novamente.", + "modal.accountsCodexAuthLink": "Autenticação do Codex", + "modal.notListed": "Provedor não listado? Adicione um personalizado", + "modal.catalogLoading": "Carregando catálogo…", + "modal.accountLogin": "Entrar", + "modal.accountLogout": "Sair", + "modal.accountAdd": "Adicionar conta", + "modal.accountManage": "Gerenciar", + "modal.accountCodexPool": "Pool de contas do ChatGPT", + "modal.accountLoggedIn": "Conectado", + "modal.accountLoggedOut": "Não conectado", + "quota.fiveHourLimit": "Limite de 5 horas", + "quota.ageMinutes": "{n}min", + "quota.ageHours": "{n}h", + "quota.ageDays": "{n}d", + "quota.observedAgo": "Observado há {age}", + "quota.observedHint": "A Meta informa o uso apenas durante uma resposta em streaming, então este é o último valor visto, não uma leitura em tempo real.", + "quota.weeklyLimit": "Limite semanal", + "quota.monthlyLimit": "Limite de 30 dias", + "quota.cursorFirstParty": "Modelos próprios", + "quota.cursorApiUsage": "Uso da API", + "quota.totalSubscriptionCredits": "Total de créditos da assinatura", + "quota.creditsBalance": "Saldo de créditos", + "quota.creditsPeriodEnds": "O período de cobrança termina em {date}", + "quota.usedPercent": "{pct}% usado", + "quota.limitReached": "Limite atingido", + "quota.resetsToday": "Redefine hoje às {time}", + "quota.resetsTomorrow": "Redefine amanhã às {time}", + "quota.resetsAt": "Redefine {when}", + "quota.resetsRelativeMinutes": "Redefine em {n} min", + "quota.resetsRelativeHours": "Redefine em {n} h", + "pws.status.ready": "Pronto", + "pws.status.needsSetup": "Requer configuração", + "pws.status.needsAttention": "Requer atenção", + "pws.auth.chatgptPassthrough": "Passthrough do ChatGPT", + "pws.auth.noKey": "Sem chave necessária", + "pws.freeTitle": "Preço gratuito (uma chave ainda pode ser necessária)", + "pws.localTitle": "Runtime local", + "pws.modelCountOne": "1 modelo", + "pws.modelCount": "{count} modelos", + "pws.rail.suffixDefault": " · padrão", + "pws.rail.suffixLocal": " · local", + "pws.rail.suffixFree": " · grátis", + "pws.rail.selectAria": "Selecionar {name} — {status}{suffix}", + "pws.searchPlaceholder": "Buscar provedores…", + "pws.filterAria": "Filtrar provedores", + "pws.providerFiltersAria": "Filtros de provedores", + "pws.filters": "Filtros", + "pws.filterStatus": "Status", + "pws.pricing": "Preço", + "pws.paid": "Pago", + "pws.filterType": "Tipo", + "pws.type.cloud": "Nuvem", + "pws.type.local": "Local", + "pws.type.selfHosted": "Autohospedado", + "pws.type.login": "Login", + "pws.sort": "Ordenar", + "pws.sortProvidersAria": "Ordenar provedores", + "pws.sort.az": "A–Z", + "pws.sort.za": "Z–A", + "pws.sort.freePaid": "Grátis primeiro", + "pws.sort.paidFree": "Pagos primeiro", + "pws.sort.accountsFirst": "Contas primeiro", + "pws.resetAll": "Limpar tudo", + "pws.providerList": "Lista de provedores", + "pws.providersAria": "Provedores", + "pws.groupReady": "Prontos ({count})", + "pws.groupNeedsSetup": "Requerem configuração ({count})", + "pws.groupDisabled": "Desativados ({count})", + "pws.noSearchResults": "Nenhum provedor corresponde à busca.", + "pws.noMatchFilters": "Nenhum provedor corresponde aos filtros.", + "pws.noProvidersConfigured": "Nenhum provedor configurado.", + "pws.workspaceMainAria": "Detalhes do provedor", + "pws.detailComingSoon": "Visão detalhada em breve — use a visão clássica para gerenciar este provedor.", + "pws.selectPrompt": "Selecione um provedor na lista.", + "pws.connectFirst": "Conecte seu primeiro provedor", + "pws.empty.browseFree": "Explorar provedores gratuitos", + "pws.empty.browseFreeDesc": "Comece sem assinatura", + "pws.empty.connectAccount": "Conectar uma conta", + "pws.empty.connectAccountDesc": "Use seu login do ChatGPT ou do provedor", + "pws.empty.addEndpoint": "Adicionar um endpoint", + "pws.empty.addEndpointDesc": "URL base e chave de API personalizadas", + "pws.tab.overview": "Visão geral", + "pws.tab.models": "Modelos", + "pws.tab.usage": "Uso", + "pws.tab.accounts": "Contas", + "pws.tab.settings": "Configurações", + "pws.connection": "Conexão", + "pws.status.connected": "Conectado", + "pws.attentionTitle": "Requer atenção", + "pws.attention.reauth": "A conta ativa precisa ser autenticada novamente", + "pws.attention.reauthForward": "A conta ativa do Codex precisa ser autenticada novamente — abra Contas para corrigir", + "pws.attention.missingCredentials": "Credenciais ausentes", + "pws.cell.auth": "Autenticação", + "pws.cell.note": "Observação", + "pws.cell.defaultModel": "Modelo padrão", + "pws.statsAria": "Estatísticas do provedor", + "pws.statsTitle": "Estatísticas", + "pws.stats.totalRequests": "Requisições (30d)", + "pws.stats.totalTokens": "Tokens (30d)", + "pws.stats.quotaUpdated": "Cota atualizada", + "pws.stats.quotaTracked": "Limites de taxa acompanhados na aba Uso.", + "pws.stats.source": "Fonte", + "pws.usageLast30d": "Uso (últimos 30 dias)", + "pws.estimatedCost": "Custo estimado", + "pws.costDisclaimer": "Estimativa pelo preço de tabela da API, não uma cobrança real.", + "pws.unresolvedRequestedModel": "Inclui uso de modelo solicitado não resolvido", + "pws.currentAccountUsage": "Uso da conta atual", + "pws.quotaUnsupported": "A consulta de cota não é compatível com esta conta.", + "pws.quotaUnobserved": "Nenhuma observação de uso ainda.", + "pws.quotaCheckCompleted": "Verificação de cota concluída", + "pws.modelBreakdown": "Detalhamento por modelo", + "pws.col.model": "Modelo", + "pws.col.cost": "Custo est.", + "pws.col.tokens": "Tokens", + "pws.col.requests": "Req.", + "pws.col.share": "Part.", + "pws.tokenInput": "Entrada", + "pws.tokenOutput": "Saída", + "pws.metricRequests": "requisições", + "pws.metricTokens": "tokens", + "pws.usageUnavailable": "Nenhum uso registrado ainda.", + "pws.rateLimits": "Limites de taxa", + "pws.quotaUnavailable": "Sem dados de cota para este provedor.", + "pws.accountQuotaUnavailable": "Dados de limite de taxa temporariamente indisponíveis; exibindo os últimos valores conhecidos, quando houver.", + "pws.quotaFailure.account_unavailable": "Os detalhes da conta estão indisponíveis para esta verificação de cota.", + "pws.quotaFailure.access_denied": "O provedor negou acesso aos dados de cota.", + "pws.quotaFailure.rate_limited": "O provedor limitou a taxa da verificação de cota.", + "pws.quotaFailure.upstream_error": "O provedor não conseguiu concluir a verificação de cota.", + "pws.quotaFailure.redirect_blocked": "O endpoint de cota retornou um redirecionamento bloqueado.", + "pws.quotaFailure.destination_blocked": "O destino da cota foi bloqueado pela política de rede.", + "pws.quotaFailure.dns_failed": "Não foi possível resolver o hostname da cota.", + "pws.quotaFailure.timeout": "A requisição de cota expirou.", + "pws.quotaFailure.transport_error": "A conexão de cota falhou.", + "pws.quotaFailure.response_unusable": "O provedor retornou dados de cota inutilizáveis.", + "pws.selected": "Selecionado", + "pws.copyModelId": "Copiar ID", + "pws.modelCopied": "Copiado!", + "pws.modelsAvailable": "{count} disponíveis", + "pws.modelSearchPlaceholder": "Filtrar modelos…", + "pws.modelsLoading": "Carregando modelos…", + "pws.modelsLoadFailed": "Não foi possível carregar os modelos.", + "pws.modelsNeedsReauth": "A conta precisa fazer login novamente para a descoberta de modelos ao vivo funcionar. Exibindo os modelos configurados por enquanto.", + "pws.modelsConfiguredFallback": "Exibindo os modelos configurados (descoberta ao vivo indisponível).", + "pws.modelsTruncated": "Exibindo os primeiros {shown} de {total} modelos. Use o filtro para refinar a lista.", + "pws.retry": "Tentar novamente", + "pws.noModels": "Nenhum modelo descoberto para este provedor.", + "pws.noModelMatch": "Nenhum modelo corresponde ao filtro.", + "pws.adapterBaseRequired": "Adaptador e URL base são obrigatórios.", + "pws.addAccount": "Adicionar conta", + "pws.addKey": "Adicionar chave de API", + "pws.apiKeys": "Chaves de API", + "pws.authMode": "Modo de autenticação", + "pws.availableAccounts": "Contas disponíveis", + "pws.accountOrdinal": "Conta {count}", + "pws.accountsLoading": "Carregando contas…", + "pws.accountsLoadFailed": "Não foi possível carregar as contas.", + "pws.retryAccounts": "Tentar novamente", + "pws.noAccounts": "Nenhuma conta conectada ainda.", + "pws.cockpitImportDescription": "Importe uma exportação JSON do Antigravity do Cockpit Tools a partir deste dispositivo. O conteúdo do arquivo não é exibido.", + "pws.cockpitImportFileLabel": "Exportação JSON do Antigravity do Cockpit Tools", + "pws.cockpitImportChooseFile": "Escolher arquivo JSON", + "pws.cockpitImporting": "Importando…", + "pws.cockpitImportInvalid": "O arquivo selecionado não é uma exportação JSON válida ou é grande demais.", + "pws.cockpitImportFailed": "Não foi possível concluir a importação das contas.", + "pws.cockpitImportComplete": "Importação concluída: {imported} importadas, {updated} atualizadas, {failed} com falha, {unsupported} sem suporte.", + "pws.accountSwitching": "Alternando…", + "pws.accountCurrent": "Conta atual", + "pws.accountPausedHint": "Excluída da seleção automática, das novas tentativas, da recuperação de cooldown, da seleção manual e da renovação proativa de token até ser retomada.", + "pws.defaultModelNone": "Nenhum (usar o padrão do provedor)", + "pws.discardSettings": "Descartar", + "pws.jsonEditorDesc": "Edite a configuração JSON bruta do provedor. As alterações são salvas imediatamente.", + "pws.jsonEditorTitle": "Editor JSON — {name}", + "pws.jsonRestore": "Restaurar", + "pws.jsonSave": "Salvar", + "pws.loggedInTitle": "Conectado", + "pws.notLoggedInTitle": "Não conectado", + "pws.note": "Observação", + "pws.allowPrivateNetwork": "Permitir rede local/privada", + "pws.liveModels": "Descobrir modelos a partir do provedor", + "pws.liveModelsDesc": "Busca o catálogo de modelos ao vivo do provedor. Desative para usar apenas os modelos configurados/estáticos.", + "pws.xaiChatOptIn": "Usar Chat Completions para Grok 4.5 e 4.6", + "pws.xaiChatOptInDesc": "Desativado seleciona Responses. Requisições OAuth Responses o usam por padrão. Os demais modelos Grok e o comportamento por plano permanecem inalterados.", + "pws.xaiChatOptInMixed": "Apenas um modelo usa Chat.", + "pws.cursorTransport": "Transporte do Cursor", + "pws.cursorTransportHttp2": "HTTP/2 (padrão)", + "pws.cursorTransportHttp1": "HTTP/1.1 (compatibilidade com proxy)", + "pws.cursorTransportDesc": "Use HTTP/1.1 quando seu proxy não conseguir transportar de forma confiável o stream HTTP/2 do Cursor.", + "pws.optionalPlaceholder": "Opcional", + "pws.providerId": "ID do provedor", + "pws.reauth": "Requer nova autenticação", + "pws.reauthenticate": "Autenticar novamente", + "pws.copyDoctor": "Copiar ocx doctor", + "pws.doctorCopied": "Copiado", + "pws.doctorCopyUnavailable": "Área de transferência indisponível", + "pws.healthCooldownHint": "Aguarde o fim do cooldown. Não teste esta conta ainda.", + "pws.healthLabel.rateLimited": "Limite de requisições atingido", + "pws.healthLabel.quotaLimited": "Cota esgotada", + "pws.healthLabel.reauthRequired": "Reautenticação necessária", + "pws.healthLabel.refreshFailed": "Falha na renovação", + "pws.healthLabel.metadataMismatch": "Metadados divergentes", + "pws.healthLabel.validationPending": "Validação pendente", + "pws.healthSummary.validationPending": "{provider} {account}: registrada com a cota esgotada. Após a cota se recuperar, atualize as cotas para executar uma pequena requisição de validação. O roteamento permanece desativado até que ela tenha êxito.", + "pws.healthLabel.credentialConflict": "Conflito de credenciais", + "pws.healthSummary.rateLimited": "{provider} {account}: limite de requisições atingido até {until}. O roteamento desta conta fica pausado até lá.", + "pws.healthSummary.quotaLimited": "{provider} {account}: cota esgotada até {until}. O roteamento desta conta fica pausado até lá.", + "pws.healthSummary.reauthRequired": "{provider} {account}: reautenticação necessária.", + "pws.healthSummary.credentialConflict": "{provider} {account}: conflito de credenciais.", + "pws.healthSummary.metadataMismatch": "{provider} {account}: metadados divergentes.", + "pws.healthSummary.staleCredentials": "{provider} {account}: credenciais incompletas.", + "pws.removeConfirm": "Remover", + "pws.removeConfirmBody": "Remover o provedor \"{name}\"? Esta ação não pode ser desfeita.", + "pws.removeDefaultConfirmBody": "Remover o provedor padrão \"{name}\"? \"{defaultProvider}\" passará a ser o provedor padrão. Esta ação não pode ser desfeita.", + "pws.removeConfirmTitle": "Remover provedor", + "pws.saveSettings": "Salvar", + "pws.pacingTitle": "Ritmo de requisições", + "pws.pacingDesc": "Distribui uniformemente o início das requisições de saída deste provedor. Respostas em streaming podem se sobrepor.", + "pws.pacingEnabled": "Ativado", + "pws.pacingRpm": "Requisições por minuto", + "pws.pacingConcurrency": "Máximo de requisições simultâneas", + "pws.pacingRpmUnit": "RPM", + "pws.pacingCapUnit": "×", + "pws.pacingDelay": "Intervalo mínimo (ms)", + "pws.pacingSlowerWins": "Prevalece o limite mais lento do provedor. As substituições por modelo só podem acrescentar mais atraso ou reduzir o limite de simultaneidade.", + "pws.pacingQueued": "na fila", + "pws.pacingNextSlot": "até o próximo slot", + "pws.pacingLastModel": "último modelo", + "pws.pacingNone": "Nenhum", + "pws.pacingModelOverrides": "Substituições por modelo", + "pws.pacingModel": "Modelo", + "pws.pacingAdd": "Adicionar substituição", + "pws.pacingRemove": "Remover", + "pws.pacingRemoveModel": "Remover a substituição de ritmo de requisições de {model}", + "pws.pacingRuleRequired": "Ative o ritmo de requisições somente após definir um limite do provedor ou uma substituição por modelo.", + "pws.pacingCapInvalid": "O máximo de requisições simultâneas deve ser um número inteiro igual ou maior que 1.", + "pws.saving": "Salvando…", + "pws.settingsSaved": "Configurações salvas.", + "pws.accountModeSaved": "Modo de conta salvo.", + "pws.accountModeFailed": "Não foi possível alternar o modo de conta.", + "pws.accountModeConfirm": "Alternar o modo de conta da OpenAI? As conversas em andamento serão reatribuídas ao conjunto de contas do outro modo, e o uso de cota passará a ser contabilizado no novo modo.", + "pws.settingsUnsavedBar": "Você tem alterações não salvas.", + "pws.unsavedLeaveBody": "Você tem alterações não salvas. Salvar antes de sair?", + "pws.unsavedLeaveTitle": "Alterações não salvas", + "pws.attentionRequired": "Requer atenção", + "pws.attentionAria": "{name}: {reason}", + "pws.missingCredentials": "Credenciais ausentes", + "pws.editJsonDesc": "Edite a configuração bruta do proxy em JSON", + "pws.updatesUnavailable": "As atualizações de provedores não estão disponíveis.", + "pws.dashboard.title": "Visão geral dos provedores", + "pws.dashboard.subtitle": "Gerencie todos os seus provedores de modelos em um só lugar.", + "pws.dashboard.rateLimits": "LIMITES DE REQUISIÇÕES", + "pws.capacity.estimate": "Estimativa do pool por pesos configurados", + "pws.capacity.currentAccount": "Conta efetiva atual", + "pws.capacity.nextRecovery": "Próxima recuperação de capacidade", + "pws.capacity.recoveryShare": "+{percent}% de capacidade do pool", + "pws.capacity.incomplete": "Cobertura incompleta: {excluded} conta(s) excluída(s)", + "pws.capacity.uncalibratedPlan": "{count} conta(s) em um plano não calibrado são contadas com o peso de assento de referência, portanto esta estimativa pode ser conservadora", + "pws.capacity.partial": "Cobertura parcial de janelas: {count} conta(s) não informam todas as janelas de limite exibidas", + "pws.capacity.windowPartial": "Parcial", + "pws.capacity.windowPartialA11y": "{window}: cobertura de contas incompleta", + "pws.dashboard.recentlyUsed": "USADOS RECENTEMENTE", + "pws.dashboard.requests": "{count} requisições", + "pws.dashboard.checkedAgo": "Verificado {time}", + "pws.dashboard.noQuota": "Sem dados de cota", + "pws.dashboard.noUsage": "Ainda sem dados de uso", + "pws.dashboard.noRateLimits": "Ainda sem dados de limites de requisições", + "pws.allProviders": "Visão geral dos provedores", + "pws.enabledLabel": "Ativado", + "pws.testConnection": "Testar conexão", + "pws.testing": "Testando…", + "pws.connectionOk": "Conexão OK", + "pws.connectionFailed": "Falha na conexão", + "pws.connectionNotApplicable": "Não se aplica — este provedor usa um catálogo de modelos estático.", + "pws.editSettings": "Editar configurações", + "pws.viewUsage": "Ver uso detalhado", + "pws.allSystemsOk": "Tudo funcionando normalmente", + "pws.apiKeyConfigured": "Chave de API configurada", + "pws.addApiKey": "Adicionar chave de API", + "pws.loggedInAs": "Conectado como {email}", + "pws.notLoggedIn": "Não conectado", + "pws.passthrough": "Passthrough do Codex", + "pws.notes": "OBSERVAÇÕES", + "pws.notePlaceholder": "Adicione uma observação sobre este provedor...", + "pws.noteSaved": "Observação salva", + "pws.authSummary": "AUTENTICAÇÃO", + "time.justNow": "Agora mesmo", + "time.notChecked": "Não verificado", + "time.minutesAgo": "há {n} min", + "time.hoursAgo": "há {n} h", + "time.daysAgo": "há {n} d", + "modal.noMatch": "Nenhuma correspondência.", + "modal.oauthDefaultNote": "Entre com sua conta — sem necessidade de chave de API.", + "modal.oauthComingSoon": "O login OAuth para {label} chega na próxima atualização. Por enquanto, use uma chave de API.", + "modal.oauthComingSoonShort": "O login OAuth para este provedor chega na próxima atualização — por enquanto, use uma chave de API.", + "modal.useApiKeyInstead": "Usar uma chave de API", + "modal.setupGuide": "Guia de configuração", + "modal.setupStep1Prefix": "Acesse o", + "modal.setupDashboardLink": "painel de {label}", + "modal.setupStep1Suffix": "e copie sua chave de API", + "modal.setupStep2": "Cole-a no campo de chave de API abaixo", + "modal.setupStep3": "Clique em Adicionar provedor — os modelos são descobertos automaticamente", + "modal.namePlaceholder": "ex.: openrouter", + "modal.duplicateWarn": "O provedor \"{name}\" já existe e será sobrescrito.", + "modal.forwardHintPrefix": "Não é necessária chave — o proxy encaminha suas credenciais de", + "modal.forwardCredentials": "codex login", + "modal.forwardHintSuffix": "para este provedor.", + "modal.localHint": "Nenhuma chave de API é armazenada. Isto adiciona o catálogo estático público de modelos do Cursor para o Codex, mas o transporte ao vivo do Cursor e a execução nativa de arquivos/shell permanecem desativados até serem auditados.", + "modal.getApiKey": "Obtenha sua chave de API de {label}", + "modal.apiKey": "Chave de API", + "modal.apiKeyTransport": "Cabeçalho da chave de API", + "modal.apiKeyTransportNative": "x-api-key (nativo da Anthropic)", + "modal.apiKeyTransportBearer": "Authorization: Bearer", + "modal.apiKeyPlaceholder": "sk-… (ou $ENV_VAR)", + "modal.defaultModelPlaceholder": "ex.: gpt-5.5", + "modal.baseUrlPlaceholder": "https://...", + "modal.baseUrlPlaceholderError": "A URL base contém um {placeholder} não resolvido. Substitua-o pelo valor real.", + "modal.baseUrlPlaceholderHint": "Substitua o {placeholder} na URL base pelo ID real da sua conta antes de adicionar.", + "modal.adding": "Adicionando…", + "modal.useOauthLogin": "← Usar login OAuth", + "nav.codexAuth": "Autenticação do Codex", + "nav.codexSet": "Config. do Codex", + "codexSet.tab.multiauth": "Multiautenticação", + "codexSet.tab.prompt": "Prompt", + "codexSet.prompt.title": "Camadas de prompt", + "codexSet.prompt.timing": "Aplica-se a sessões recém-iniciadas. As sessões em andamento mantêm suas configurações de prompt atuais.", + "codexSet.prompt.staleRevision": "A configuração foi alterada em outro lugar. A lista foi recarregada.", + "codexSet.prompt.writeFailed": "Não foi possível salvar a alteração.", + "codexSet.prompt.loadFailed": "Não foi possível carregar as camadas de prompt.", + "codexSet.prompt.repair": "Reparar", + "codexSet.prompt.repairFailed": "Não foi possível concluir o reparo.", + "codexSet.drift.journalPresent": "Uma gravação anterior não foi concluída. A recuperação é executada automaticamente na próxima gravação.", + "codexSet.drift.projectionStale": "As camadas salvas e o valor em config.toml divergem. O reparo reescreve o valor a partir das suas camadas.", + "codexSet.drift.storeMissing": "O arquivo de camadas sumiu, mas as instruções permanecem em config.toml. O reparo mantém o texto como uma única camada e grava um backup antes.", + "codexSet.drift.ownedMalformed": "A linha gerada em config.toml foi remodelada manualmente e, por isso, não é mais seguro reescrevê-la.", + "codexSet.custom.adoptUnsupported": "O valor em {path}, linha {line}, não é uma string de linha única e não pode ser importado. Mova-o manualmente para gerenciá-lo aqui.", + "codexSet.prompt.unreadable": "O arquivo de configuração do Codex existe, mas não pôde ser lido; por isso as alterações são recusadas.", + "codexSet.layer.permissions": "Permissões", + "codexSet.layer.collaboration": "Modo de colaboração", + "codexSet.layer.environment": "Contexto do ambiente", + "codexSet.layer.apps": "Apps", + "codexSet.layer.skills": "Skills", + "codexSet.prompt.extensionsUnknown": "As extensões podem adicionar suas próprias camadas. O Codex não as expõe, portanto não é possível listá-las aqui.", + "codexSet.group.transition": "Avisos de transição", + "codexSet.group.transitionDesc": "Estes anunciam uma mudança em vez de descrever um estado, por isso aparecem apenas quando a sessão entra em tempo real ou troca de modelo.", + "codexSet.custom.slotNote": "As camadas personalizadas são unidas em uma única seção, nesta ordem.", + "codexSet.row.alwaysOn": "Sempre ativa", + "codexSet.row.onChange": "Disparada em mudanças", + "codexSet.row.featureGated": "Configurada em [features]", + "codexSet.row.openFeatures": "Abrir configurações", + "codexSet.dialog.setValue": "{value} (padrão {fallback})", + "codexSet.dialog.copyKey": "Copiar chave", + "codexSet.dialog.unknownLayer": "Esta versão não tem descrição para esta camada. Ela vem de um runtime do Codex mais recente que o dashboard.", + "codexSet.custom.heading": "Camadas personalizadas", + "codexSet.custom.add": "+ Adicionar camada", + "codexSet.custom.newTitle": "Nova camada", + "codexSet.custom.editTitle": "Editar camada", + "codexSet.custom.titleLabel": "Título", + "codexSet.custom.bodyLabel": "Instruções", + "codexSet.custom.bodySize": "{bytes} de {max} bytes", + "codexSet.custom.normalized": "Tabulações viraram quatro espaços e as quebras de linha viraram LF.", + "codexSet.custom.titleRequired": "Informe um título.", + "codexSet.custom.titleTooLong": "O título tem {count} caracteres; o limite é {max}.", + "codexSet.custom.titleMultiline": "O título deve ter uma única linha.", + "codexSet.custom.bodyTooLarge": "Esta camada tem {bytes} bytes; o limite é {max}.", + "codexSet.custom.composedTooLarge": "Somadas, as camadas ativadas teriam {bytes} bytes, acima do limite.", + "codexSet.custom.invalidCharacter": "Um caractere de controle na posição {position} não pode ser salvo.", + "codexSet.custom.discardPrompt": "Descartar suas alterações?", + "codexSet.custom.discardOthersAndSave": "Descartar as edições não salvas das outras camadas e salvar esta camada?", + "codexSet.custom.keepEditing": "Continuar editando", + "codexSet.custom.delete": "Excluir {title}", + "codexSet.custom.deleteConfirm": "Excluir esta camada? Não há como desfazer.", + "codexSet.custom.layerGone": "Essa camada foi removida em outro lugar, então o editor foi fechado.", + "codexSet.custom.deleteConfirmNamed": "Excluir “{title}”? Não há como desfazer.", + "codexSet.custom.moveUp": "Mover {title} para cima", + "codexSet.custom.prevLayer": "Camada anterior", + "codexSet.custom.nextLayer": "Próxima camada", + "codexSet.custom.navPosition": "{position} / {total}", + "codexSet.custom.moveDown": "Mover {title} para baixo", + "codexSet.custom.limitReached": "Você pode manter até {max} camadas personalizadas.", + "codexSet.custom.notOwned": "developer_instructions foi gravado fora do opencodex e, por isso, não é editado aqui. Importe-o para gerenciá-lo como uma camada.", + "codexSet.custom.adopt": "Importar instruções existentes", + "codexSet.custom.adoptConfirm": "Importar como camada", + "codexSet.custom.adoptRefused": "Não foi possível importar o valor existente.", + "codexSet.custom.baseReplaced": "model_instructions_file está definido como {path}, ou seja, algo fora do opencodex substituiu o prompt base.", + "codexSet.lint.identity": "Isto declara uma identidade diferente da que o Codex estabelece.", + "codexSet.lint.foreignTool": "As ferramentas vêm do registro; nomeá-las aqui não as cria.", + "codexSet.lint.placeholder": "Nenhum mecanismo de template é executado sobre as instruções, então isto é enviado literalmente.", + "codexSet.lint.applyPatch": "apply_patch é definido pelo registro de ferramentas, não pelas instruções.", + "codexSet.lint.approvalVocab": "O Codex injeta seu próprio vocabulário de aprovação; isto pode contradizê-lo.", + "codexSet.lint.environment": "Os fatos do ambiente são gerados depois e podem contradizer isto.", + "codexSet.lint.size": "Esta camada tem mais de 8 KB. Ela ainda é salva, mas consome tokens a cada requisição.", + "codexSet.preset.blank": "Camada em branco", + "codexSet.preset.concise.name": "Saída concisa", + "codexSet.preset.concise.description": "Respostas curtas, sem preâmbulo, com formatação mínima.", + "codexSet.preset.concise.provenance": "Adaptado das diretrizes de concisão do Claude Code. Redação nossa, não uma cópia.", + "codexSet.preset.planFirst.name": "Planejar antes de editar", + "codexSet.preset.planFirst.description": "Apresente o plano e depois faça a alteração.", + "codexSet.preset.planFirst.provenance": "Adaptado da postura de planejamento do Claude Code. Redação nossa, não uma cópia.", + "codexSet.preset.explainWhy.name": "Explicar o raciocínio", + "codexSet.preset.explainWhy.description": "Diga o porquê, não apenas o quê.", + "codexSet.preset.explainWhy.provenance": "Adaptado do estilo de confirmação do Grok Build. Redação nossa, não uma cópia.", + "codexSet.preset.testFirst.name": "Testar primeiro", + "codexSet.preset.testFirst.description": "Escreva o teste que falha antes da correção.", + "codexSet.preset.testFirst.provenance": "Adaptado de práticas comuns de agentes. Redação nossa, não uma cópia.", + "codexSet.preset.korean.name": "Respostas em coreano", + "codexSet.preset.korean.description": "Responda em coreano, qualquer que seja o idioma da requisição.", + "codexSet.preset.korean.provenance": "Escrito para o opencodex a partir de um pedido recorrente de usuários. Redação nossa, não uma cópia.", + "codexSet.dialog.class": "Tipo", + "codexSet.dialog.key": "Chave de configuração", + "codexSet.dialog.fileValue": "Valor neste arquivo", + "codexSet.dialog.absentDefault": "não definido (padrão {value})", + "codexSet.dialog.noRenderedText": "O Codex não expõe o texto montado de uma camada nativa; por isso este diálogo descreve a camada e indica sua chave, em vez de mostrar seu conteúdo.", + "codexSet.dialog.sourceText": "Texto enviado ao modelo", + "codexSet.dialog.sourceBytes": "{bytes} bytes", + "codexSet.dialog.notRendered": "Esta camada não enviou nada no turno que lemos. As seções só são reenviadas quando mudam, então uma camada inalterada fica ausente de uma única amostra.", + "codexSet.dialog.emptySource": "O arquivo em {path} existe, mas está vazio, então esta camada não envia nada.", + "codexSet.dialog.notExposed": "O prompt base trafega fora da lista de mensagens que o Codex consegue imprimir, então não pode ser exibido aqui. É possível substituí-lo por meio de model_instructions_file.", + "codexSet.dialog.unmapped": "Esta camada não tem mapeamento confirmado na saída do prompt, então seu texto ainda não pode ser exibido aqui.", + "codexSet.dialog.textUnavailable": "Não foi possível ler o prompt do Codex nesta máquina, então o texto está indisponível.", + "codexSet.class.base": "Instruções base", + "codexSet.class.config-toggle": "Alternável aqui", + "codexSet.class.feature-gated": "Controlada por recurso", + "codexSet.class.runtime-conditional": "Condicional ao runtime", + "codexSet.class.extension-unknown": "Camada de extensão", + "codexSet.layer.base-instructions": "Instruções base", + "codexSet.layer.model-switch": "Aviso de troca de modelo", + "codexSet.layer.personality": "Personalidade", + "codexSet.layer.context-window-guidance": "Orientação sobre a janela de contexto", + "codexSet.layer.realtime": "Tempo real", + "codexSet.layer.agents-md": "AGENTS.md", + "codexSet.layer.environments-instructions": "Ambientes", + "codexSet.layer.plugins": "Plugins", + "codexSet.layer.tools": "Ferramentas", + "codexSet.layer.multi-agent-mode": "Modo multiagente", + "codexSet.layer.git-attribution": "Atribuição de commits", + "codexSet.about.base-instructions": "As instruções do próprio Codex. Elas viajam com a própria requisição e não podem ser desativadas.", + "codexSet.about.model-switch": "Adicionada quando a sessão troca de modelo no meio da conversa.", + "codexSet.about.personality": "Orientação de tom e voz, controlada por uma feature flag.", + "codexSet.about.context-window-guidance": "Orientação sobre o orçamento de contexto restante, controlada por uma feature flag.", + "codexSet.about.realtime": "Adicionada em sessões em tempo real.", + "codexSet.about.agents-md": "Os arquivos AGENTS.md do seu projeto. Esta página apenas informa a camada; nunca edita a documentação do seu projeto.", + "codexSet.about.permissions": "Explica as configurações de sandbox e de aprovação em vigor.", + "codexSet.about.collaboration": "Explica o modo de colaboração ativo.", + "codexSet.about.environment": "Diretório de trabalho, plataforma e outros fatos do ambiente.", + "codexSet.about.environments-instructions": "Orientação para ambientes de execução adiada, controlada por uma feature flag.", + "codexSet.about.apps": "Como usar os apps conectados.", + "codexSet.about.plugins": "Adicionada quando um plugin é selecionado ou quando qualquer plugin anuncia uma capacidade.", + "codexSet.about.tools": "Descrições de ferramentas adiadas, controladas por uma feature flag.", + "codexSet.about.skills": "A lista de skills disponíveis.", + "codexSet.about.multi-agent-mode": "Instruções de subagentes, controladas por uma feature flag.", + "codexSet.about.git-attribution": "Instrui o modelo a adicionar um trailer Co-authored-by: Codex aos commits que ele escreve e uma linha Generated with Codex. aos pull requests que ele abre. O Codex resolve isso a partir da sua conta, portanto não há configuração para isso aqui nem em [features]. Quando sua conta o desativa, o Codex envia a instrução oposta em vez de não enviar nada.", + "codexSet.condition.model-switch": "Emitida apenas após uma troca de modelo no meio da sessão.", + "codexSet.condition.realtime": "Emitida apenas em uma sessão em tempo real.", + "codexSet.condition.agents-md": "Emitida quando um documento de projeto é encontrado para o diretório de trabalho.", + "codexSet.condition.plugins": "Emitida quando um plugin é selecionado ou quando qualquer plugin anuncia uma capacidade.", + "codexSet.condition.git-attribution": "Definida pela política de atribuição da sua conta.", + "codexSet.base.title": "Prompt base", + "codexSet.base.prev": "Opção anterior", + "codexSet.base.next": "Próxima opção", + "codexSet.base.position": "{position} / {total}", + "codexSet.base.swipeHint": "Deslize para o lado, use as teclas de seta ou pressione as setas para alternar entre as opções. Aplica-se a sessões recém-iniciadas.", + "codexSet.base.defaultTitle": "Prompt base do próprio Codex", + "codexSet.base.defaultBody": "O padrão não é armazenado aqui, então não há nada para editar ou excluir: escolhê-lo simplesmente remove model_instructions_file da sua configuração, e o Codex usa o prompt que já acompanha o produto.", + "codexSet.base.variantTitle": "Nome", + "codexSet.base.variantBody": "Prompt", + "codexSet.base.replacesWarning": "Isto SUBSTITUI o prompt base do Codex em vez de acrescentar a ele. Um prompt curto aqui significa um modelo com instruções curtas.", + "codexSet.base.use": "Usar este", + "codexSet.base.inUse": "Em uso", + "codexSet.base.externalBlocked": "model_instructions_file já aponta para {path}, que não foi gravado pelo opencodex. Limpe-o você mesmo antes de escolher uma opção aqui.", + "nav.api": "API", + "nav.integrations": "Integrações", + "nav.openMenu": "Abrir menu", + "nav.closeMenu": "Fechar menu", + "nav.goHome": "Ir para o painel", + "nav.collapseSidebar": "Recolher barra lateral", + "nav.expandSidebar": "Expandir barra lateral", + "pws.refreshAllQuotas": "Atualizar todas as cotas", + "pws.quotaRefreshDone": "Verificação de cotas concluída", + "integrations.subtitle": "Conecte clientes ao opencodex, gerencie credenciais e restaure a configuração dos clientes.", + "integrations.tabsLabel": "Superfícies de integração", + "integrations.tab.overview": "Visão geral", + "integrations.tab.keys": "Chaves de API", + "integrations.tab.codex": "Codex", + "integrations.tab.claude": "Claude", + "integrations.tab.grok": "Grok Build", + "integrations.tab.cursor": "Cursor", + "integrations.tab.opencode": "OpenCode", + "integrations.tab.pi": "Pi", + "integrations.tab.omp": "OMP", + "integrations.tab.hermes": "Hermes", + "integrations.tab.openclaw": "OpenClaw", + "integrations.tab.kimi": "Kimi Code", + "integrations.tab.gajae": "gjc", + "integrations.tab.dsh": "DSH", + "integrations.tab.mcode": "MiniMax Code", + "integrations.tab.zcode": "ZCode", + "integrations.tab.prime": "Prime Agent", + "integrations.tab.aside": "Aside", + "integrations.tab.raycast": "Raycast", + "integrations.tab.omo": "omo", + "integrations.tab.cline": "Cline CLI", + "integrations.tab.kilo": "Kilo", + "integrations.tab.droid": "Factory Droid", + "integrations.aside.profilesTitle": "Perfis do Aside", + "integrations.aside.profilesHint": "Escolha quais perfis recebem os modelos selecionados. O perfil ativo do Aside permanece inalterado.", + "integrations.aside.all": "Sincronizar todos os perfis", + "integrations.aside.syncNow": "Sincronizar agora", + "integrations.aside.applied": "{count} de {total} perfis aplicados", + "integrations.aside.current": "Perfil atual", + "integrations.aside.profile": "Perfil {id}", + "integrations.aside.toggle": "Sincronizar {name}", + "integrations.aside.details": "Gerenciar {name}", + "integrations.aside.back": "Todos os perfis do Aside", + "integrations.aside.empty": "Abra o Aside e crie um perfil para conectá-lo.", + "integrations.aside.partial": "Alguns perfis requerem atenção. Suas escolhas de sincronização foram salvas; verifique o estado de cada perfil.", + "integrations.aside.pending": "Escolha de sincronização salva; atualização do arquivo pendente.", + "integrations.aside.retry": "Tentar {name} novamente", + "integrations.aside.loadError": "Não foi possível carregar os perfis do Aside. Tente novamente para verificar o estado deles.", + "integrations.codex.title": "Codex CLI", + "integrations.codex.body": "A integração do Codex pertence ao serviço do proxy. Iniciar o opencodex a aplica; parar o serviço restaura o roteamento nativo.", + "integrations.codex.openService": "Abrir controles do serviço", + "integrations.state.notInstalled": "Não instalado", + "integrations.state.unknown": "Verificando…", + "integrations.detail.codexRouted": "As requisições do Codex passam por este proxy", + "integrations.detail.codexAbsent": "O Codex ainda não é roteado por este proxy", + "integrations.detail.keyCount": "{count} chave(s) emitida(s)", + "integrations.detail.keyNone": "Nenhuma chave emitida", + "integrations.detail.keyChecking": "Verificando…", + "integrations.detail.keyUnavailable": "Status da chave indisponível", + "integrations.detail.claudeOff": "A conexão está desativada", + "integrations.detail.desktopCurrent": "O Desktop está executando este perfil", + "integrations.detail.desktopStale": "O arquivo de perfil foi alterado após ser aplicado", + "integrations.detail.desktopNotServed": "O perfil existe, mas o Desktop serve outro", + "integrations.detail.desktopAbsent": "Nenhum perfil aplicado", + "integrations.detail.desktopDesiredOff": "A integração com o Claude Desktop está desativada", + "integrations.detail.desktopDesiredOffCleanupPending": "O Claude Desktop ainda está usando o gateway; limpeza pendente", + "integrations.detail.desktopDesiredOnNotApplied": "A integração está ativada, mas o Desktop não está usando o perfil do gateway", + "integrations.detail.desktopSelectedElsewhere": "O Desktop está usando outro perfil", + "integrations.detail.desktopProfileDrift": "O perfil selecionado do Desktop foi alterado", + "integrations.detail.desktopObservedUnsafe": "O perfil selecionado do Desktop não pode ser alterado com segurança", + "integrations.detail.desktopNotInstalled": "A biblioteca de configuração do Claude Desktop não está instalada", + "integrations.detail.grokModels": "{count} modelo(s) conectado(s)", + "integrations.detail.grokAbsent": "Nenhum bloco do opencodex na configuração", + "integrations.detail.cursorSeen": "O Cursor chamou este proxy recentemente", + "integrations.detail.cursorNeverSeen": "Private Inference instalado; nenhuma requisição vista ainda", + "integrations.detail.cursorAbsent": "Cursor Private Inference não encontrado", + "integrations.cursor.title": "Cursor", + "integrations.cursor.intro": "O Cursor Private Inference executa seu agente localmente e se comunica com o opencodex em loopback. O Cursor comum não consegue: o backend dele chama o endpoint personalizado e exige uma URL HTTPS pública. Esta página nunca grava no Cursor; cole os valores abaixo no Cursor você mesmo.", + "integrations.cursor.loading": "Lendo o status do Cursor…", + "integrations.cursor.unavailable": "Não foi possível ler o status do Cursor a partir do proxy.", + "integrations.cursor.detection": "Versões instaladas", + "integrations.cursor.privateInference": "Cursor Private Inference", + "integrations.cursor.regular": "Cursor (padrão)", + "integrations.cursor.detected": "Detectado", + "integrations.cursor.notFound": "Não encontrado", + "integrations.cursor.regularOnly": "Apenas o Cursor regular foi encontrado. Ele roteia endpoints personalizados pelos servidores do Cursor, então um proxy em loopback fica inacessível sem um túnel público. Consulte o guia da versão Private Inference.", + "integrations.cursor.installerIntro": "O Cursor Private Inference é uma versão separada em modo local. O canal de atualização do Cursor anuncia o instalador, versão {version}; instale-o e cole a Base URL e a chave de API abaixo em Settings > Models > Gateway.", + "integrations.cursor.installerOpen": "Baixar o instalador", + "integrations.cursor.installerUnavailable": "Não foi possível resolver o instalador do Private Inference a partir do canal de atualização do Cursor para este computador. Os valores do gateway abaixo valem depois que ele for instalado.", + "integrations.cursor.installerCheck": "Procurar o instalador do Private Inference", + "integrations.cursor.installerChecking": "Consultando o canal de atualização do Cursor…", + "integrations.cursor.nothingFound": "Nenhuma instalação do Cursor foi encontrada nos locais habituais. Se estiver instalado em outro lugar, os valores abaixo continuam valendo.", + "integrations.cursor.gateway": "Valores do gateway", + "integrations.cursor.gatewayHint": "No Cursor Private Inference, abra Settings > Models > Gateway, cole estes dois valores e clique em Refresh model list.", + "integrations.cursor.baseUrl": "Base URL", + "integrations.cursor.apiKey": "Chave de API", + "integrations.cursor.apiKeyCredential": "Uma das suas chaves de API do opencodex (este bind exige uma credencial)", + "integrations.cursor.copy": "Copiar", + "integrations.cursor.copied": "Copiado", + "integrations.cursor.connection": "Conexão", + "integrations.cursor.seen": "Última requisição do Cursor: {time} ({ua})", + "integrations.cursor.neverSeen": "Nenhuma requisição do Cursor desde que o proxy iniciou. Depois de salvar o gateway, clique em Refresh model list no Cursor.", + "integrations.cursor.models": "O que o Cursor vai mostrar", + "integrations.cursor.modelsHint": "O Cursor escolhe a escala de Reasoning a partir da própria tabela de modelos, então o opencodex só consegue prevê-la. Context lista a janela padrão e a opcional (Max Mode do Cursor).", + "integrations.cursor.ladderFromBundle": "Escalas de Reasoning lidas do bundle instalado do Cursor Private Inference {version}. Quem decide é o Cursor; o opencodex apenas informa a tabela dele.", + "integrations.cursor.ladderFromStatic": "As escalas de Reasoning são um espelho estático do Cursor 3.18.25 (nenhum bundle legível do Private Inference foi encontrado). Context lista a janela padrão e a opcional.", + "integrations.cursor.unknownVersion": "versão desconhecida", + "integrations.cursor.noControl": "—", + "integrations.cursor.singleWindow": "janela única", + "integrations.cursor.noControlTitle": "Este id não está na tabela de esforço embutida do Cursor, então o Cursor não mostra controle de Reasoning.", + "integrations.cursor.effortRowsOne": "1 linha de esforço publicada", + "integrations.cursor.effortRowsMany": "{n} linhas de esforço publicadas", + "integrations.cursor.effortRowsOff": "sem linhas de esforço", + "integrations.cursor.tableLessHint": "Linhas marcadas com — ficam sem controle de Reasoning no Cursor. Ative cursorEffortRows para publicar uma entrada no seletor por esforço (id--effort) ou defina modelDefaultReasoningEfforts no provedor para um padrão fixo.", + "integrations.cursor.colModel": "Modelo", + "integrations.cursor.colReasoning": "Raciocínio", + "integrations.cursor.colContext": "Contexto", + "integrations.cursor.guide": "Abrir o guia do Cursor Private Inference", + "integrations.dialog.codex.title": "Desativar a integração com o Codex?", + "integrations.dialog.codex.changes": "O opencodex vai remover seu roteamento de {path}, remover o perfil gerado, restaurar o catálogo de modelos nativo e reetiquetar as threads retomáveis para o Codex nativo.", + "integrations.dialog.codex.breakage": "O codex puro vai se conectar direto à OpenAI, e os modelos roteados de outros provedores vão desaparecer do Codex. O proxy e o /v1/responses continuam rodando para outros clientes.", + "integrations.dialog.codex.undo": "Ativar de novo reconstrói o catálogo roteado com os modelos disponíveis na hora e injeta o Codex novamente. O histórico de retomada é adaptado à direção correspondente, mas seus arquivos não são restaurados byte a byte.", + "integrations.dialog.codex.sideEffect": "Se você selecionou um modelo raiz roteado depois que o opencodex injetou a configuração, desativar remove essa seleção e reativar a integração não consegue reconstruí-la; selecione o modelo de novo. Se um model_provider externo controla o Codex, o opencodex remove apenas seu journal obsoleto e deixa a configuração, o catálogo e o histórico inalterados.", + "integrations.dialog.codex.confirm": "Desativar", + "integrations.dialog.grok.title": "Desativar a integração com o Grok Build?", + "integrations.dialog.grok.changes": "Apenas o bloco marcado pelo opencodex será removido de {path}. O conteúdo escrito fora do bloco permanece inalterado.", + "integrations.dialog.grok.breakage": "Desativar remove os aliases de modelo do opencodex no Grok Build. Os modelos usados com sua conta xAI continuam disponíveis.", + "integrations.dialog.grok.undo": "Se o opencodex estiver rodando em um endereço loopback, reativar grava um novo bloco com os modelos disponíveis no momento.", + "integrations.dialog.grok.confirm": "Desativar", + "integrations.dialog.desktop.title": "Desativar a integração com o Claude Desktop?", + "integrations.dialog.desktop.changes": "Se {path} contiver um perfil de gateway gerenciado pelo opencodex, o Desktop primeiro selecionará um novo perfil padrão sem credenciais e depois removerá o perfil antigo e o backup.", + "integrations.dialog.desktop.breakage": "O Claude Desktop voltará ao Claude padrão, em vez dos modelos roteados pelo opencodex.", + "integrations.dialog.desktop.undo": "Reativar regenera o perfil do opencodex a partir das suas atribuições de modelo salvas.", + "integrations.dialog.desktop.restart": "O Claude Desktop lê esta configuração apenas na inicialização. Feche-o completamente e reabra para que a mudança tenha efeito.", + "integrations.dialog.desktop.confirm": "Desativar", + "integrations.native.msg.nonLoopbackRemoved": "O Grok Build só pode ser registrado automaticamente enquanto o opencodex roda em um endereço loopback. O bloco anterior que apontava para loopback foi removido.", + "integrations.native.msg.nonLoopbackRemovedNoop": "O Grok Build só pode ser registrado automaticamente enquanto o opencodex roda em um endereço loopback. Não havia bloco anterior para remover.", + "integrations.native.msg.nonLoopbackSuperseded": "O Grok Build só pode ser registrado automaticamente enquanto o opencodex roda em um endereço loopback. Outro processo gravou um novo bloco nesse meio-tempo, então o bloco atual no arquivo não foi criado por esta requisição.", + "integrations.native.error.orphanedMarker": "{path} tem um marcador de início do opencodex, mas nenhum marcador de fim. O arquivo foi mantido inalterado porque o opencodex não consegue determinar onde seu bloco termina.", + "integrations.native.error.homeMismatch": "O home do serviço instalado não corresponde ao home atual, então o arquivo foi mantido inalterado.", + "integrations.native.error.notInstalled": "O Grok Build não está instalado, então não há nada a alterar.", + "integrations.native.error.configBusy": "A configuração está sendo salva em outro lugar e não pôde ser alterada. Tente novamente em instantes.", + "integrations.native.error.desktopUnsafeMetadata": "Os metadados do Claude Desktop em {path} não puderam ser lidos com segurança, então sua biblioteca não foi alterada.", + "integrations.native.error.desktopCleanupIncomplete": "O Claude Desktop está apontado para o modo padrão, mas ainda restam arquivos de credencial antigos do opencodex em: {paths}.", + "integrations.native.msg.desktopDisabled": "Integração com o Claude Desktop desativada.", + "integrations.native.msg.desktopEnabled": "Integração com o Claude Desktop ativada.", + "integrations.state.absent": "Não aplicado", + "integrations.state.current": "Aplicado", + "integrations.state.stale": "Atualização necessária", + "integrations.state.conflict": "Conflito", + "integrations.state.unsafe": "Não foi possível verificar", + "integrations.summary.detected": "Clientes detectados", + "integrations.summary.applied": "Clientes configurados", + "integrations.summary.stale": "Atualização necessária", + "integrations.summary.lastChange": "Última alteração", + "integrations.summary.disableAll": "Desativar todos…", + "integrations.onboarding": "Aplicar grava um bloco de provedor do opencodex depois de salvar um backup. Desativar remove apenas esse bloco, e um snapshot retido pode ser restaurado.", + "integrations.empty.title": "Nenhum cliente instalado foi detectado", + "integrations.empty.body": "Instale um cliente compatível e volte aqui para aplicar o opencodex.", + "integrations.action.apply": "Aplicar", + "integrations.action.disable": "Desativar", + "integrations.action.refresh": "Atualizar", + "integrations.action.settings": "Configurações", + "integrations.action.manageKeys": "Gerenciar chaves", + "integrations.action.restore": "Restaurar…", + "integrations.action.undo": "Desfazer", + "integrations.action.restorePoint": "Restaurar este ponto…", + "integrations.action.snapshotExpired": "Backup expirado", + "integrations.rollback.title": "Central de rollback", + "integrations.rollback.empty": "Ainda não há histórico de aplicações", + "integrations.rollback.emptyBody": "Toda gravação bem-sucedida mantém antes um snapshot do estado anterior.", + "integrations.catalog.title": "Clientes", + "integrations.rollback.older": "Operações anteriores", + "integrations.rollback.showMore": "Mostrar mais {n}", + "integrations.rollback.failed": "Não foi possível carregar o histórico de rollback.", + "integrations.rollback.delete": "Excluir", + "integrations.rollback.deleteAria": "Excluir a entrada de rollback de {at}", + "integrations.rollback.deleteNewest": "A entrada mais recente deste cliente é mantida para que você ainda possa desfazer.", + "integrations.rollback.deleteGone": "Esta entrada já foi excluída. A lista será atualizada.", + "integrations.dialog.deleteEntry.title": "Excluir esta entrada de rollback?", + "integrations.dialog.deleteEntry.changes": "Esta entrada some da lista de rollback, e qualquer backup que ela ainda guarde de {path} é apagado do disco.", + "integrations.dialog.deleteEntry.breakage": "Você não poderá mais restaurar o arquivo para este ponto. As entradas mais novas e o próprio arquivo não são afetados.", + "integrations.dialog.deleteEntry.undo": "Isso não pode ser desfeito. A entrada mais recente de cada cliente é mantida e não pode ser excluída.", + "integrations.dialog.deleteEntry.confirm": "Excluir entrada", + "integrations.restore.title": "Restaurar este snapshot?", + "integrations.restore.body": "O arquivo atual recebe um backup primeiro, e então o snapshot selecionado o substitui.", + "integrations.restore.driftTitle": "Foram detectadas edições mais novas", + "integrations.restore.driftBody": "As alterações feitas depois deste snapshot receberão backup, e então o arquivo será substituído.", + "integrations.restore.confirm": "Restaurar", + "integrations.restore.confirmDrift": "Fazer backup das edições novas e restaurar", + "integrations.restore.pending": "Restaurando…", + "integrations.restore.manual": "A restauração automática falhou: {reason}. Restaure manualmente a partir de {path}.", + "integrations.error.load": "Não foi possível carregar o estado das integrações.", + "integrations.error.stale": "A última atualização falhou. Os valores abaixo podem estar desatualizados.", + "integrations.error.busy": "Outra alteração para este cliente ainda está em andamento. Tente novamente em instantes.", + "integrations.error.conflict": "A configuração mudou depois que o opencodex a gravou. Nada foi removido.", + "integrations.error.unsafe": "A configuração não pode ser alterada com segurança.", + "integrations.error.generic": "A alteração da integração falhou. Seu estado anterior foi mantido.", + "integrations.error.nonLoopback": "{client} só consegue alcançar um proxy em localhost: a configuração dele não tem onde colocar o cabeçalho de admissão que um bind remoto exige, então gravá-lo manualmente também não ajudaria. Dê a ele acesso loopback, por meio de um túnel ou de um encaminhador local.", + "integrations.status.installed": "Instalado", + "integrations.status.notInstalled": "Não instalado", + "integrations.status.appliedAt": "Aplicado", + "integrations.status.supersededStore": "Este cliente agora lê seus provedores de {path}, que o opencodex não grava, então ativá-lo aqui não mudaria nada do que ele carrega.", + "integrations.status.candidateConflict": "Outro arquivo de configuração do Kilo, {path}, também define provider.opencodex. Remova provider.opencodex desse arquivo antes de aplicar.", + "integrations.status.backup": "Backup", + "integrations.status.lastRestore": "Última restauração", + "integrations.status.unknown": "Desconhecido", + "integrations.bulk.title": "Desativar as integrações de clientes aplicadas?", + "integrations.bulk.body": "Apenas o bloco pertencente ao opencodex é removido. Um snapshot anterior à gravação é mantido para cada cliente.", + "integrations.bulk.partial": "Alguns clientes não puderam ser desativados: {clients}", + "integrations.bulk.success": "As integrações de clientes aplicadas foram desativadas.", + "integrations.retention.degraded": "A limpeza de backups está atrasada; backups mais antigos ainda podem estar no disco.", + "integrations.error.residual": "O arquivo pode estar em um estado intermediário: {message} Restaure-o a partir de {path}.", + "integrations.error.residualNoSnapshot": "{message} A recuperação automática não foi concluída. Verifique a configuração do cliente antes de tentar de novo.", + "integrations.error.recover": "{message} Há um backup em {path}.", + "integrations.kind.apply": "Aplicado", + "integrations.kind.disable": "Desativado", + "integrations.kind.refresh": "Atualizado", + "integrations.kind.restore": "Restaurado", + "integrations.kind.overwrite": "Sobrescrito", + "integrations.dialog.overwrite.title": "Substituir o bloco nesta configuração?", + "integrations.dialog.overwrite.changesUnowned": "Um bloco que não foi escrito por nós ocupa as configurações de que o opencodex precisa em {path}. Aplicar o substitui pelo bloco que o opencodex gravaria.", + "integrations.dialog.overwrite.changesForeign": "Sua edição dentro do bloco do opencodex em {path} será descartada e substituída pelo bloco que o opencodex gravaria.", + "integrations.dialog.overwrite.breakage": "Tudo o que o outro bloco configurava deixa de valer. O restante do arquivo não é tocado.", + "integrations.dialog.overwrite.undo": "Um snapshot é salvo antes, então isto aparece na lista de rollback abaixo e pode ser desfeito.", + "integrations.dialog.overwrite.confirm": "Substituir", + "integrations.action.overwrite": "Substituir", + "integrations.preview.loading": "Carregando o plano exato de alterações…", + "integrations.preview.failed": "Não foi possível carregar o plano de alterações. Nada foi alterado.", + "integrations.preview.stale": "A integração mudou enquanto você confirmava. Revise o plano atualizado e confirme novamente.", + "integrations.dialog.apply.title": "Aplicar esta integração?", + "integrations.dialog.apply.changes": "O opencodex vai adicionar suas configurações de provedor gerenciadas à configuração do cliente.", + "integrations.dialog.apply.breakage": "O cliente passará a usar os modelos expostos atualmente por esta instância do opencodex.", + "integrations.dialog.apply.undo": "Um backup é salvo antes, e a alteração pode ser desfeita pela central de rollback.", + "integrations.dialog.apply.confirm": "Aplicar", + "integrations.dialog.disable.title": "Desativar esta integração?", + "integrations.dialog.disable.changes": "Apenas as configurações pertencentes ao opencodex serão removidas.", + "integrations.dialog.disable.breakage": "O cliente deixará de usar os modelos roteados por esta instância do opencodex.", + "integrations.dialog.disable.undo": "Um backup é salvo antes, e a alteração pode ser desfeita pela central de rollback.", + "integrations.dialog.disable.confirm": "Desativar", + "integrations.plan.heading": "Plano de alterações do servidor", + "integrations.plan.operation": "Operação: {operation}", + "integrations.plan.operation.apply": "Aplicar", + "integrations.plan.operation.overwrite": "Substituir", + "integrations.plan.operation.disable": "Desativar", + "integrations.plan.operation.restore": "Restaurar", + "integrations.plan.change.add": "Adicionar", + "integrations.plan.change.replace": "Substituir", + "integrations.plan.change.remove": "Remover", + "integrations.plan.change.snapshot": "Salvar backup", + "integrations.plan.change.ownership": "Atualizar o registro de propriedade", + "integrations.plan.change.journal": "Registrar entrada de rollback", + "integrations.plan.foreign.none": "Nenhuma edição externa foi detectada.", + "integrations.plan.foreign.unowned": "Um bloco de configurações que não pertence ao opencodex será substituído.", + "integrations.plan.foreign.foreignEdit": "As edições dentro do bloco gerenciado pelo opencodex serão substituídas.", + "integrations.plan.foreign.drift": "O arquivo mudou desde este ponto de rollback.", + "integrations.plan.noop.applied": "Nenhuma alteração é necessária no documento do cliente gerenciado; as configurações da integração já estão presentes.", + "integrations.plan.noop.disabled": "Nenhuma alteração é necessária no documento do cliente gerenciado; as configurações da integração já estão ausentes.", + "integrations.plan.noop.profilePreference": "A preferência de sincronização do perfil ainda será salva quando você confirmar.", + "integrations.plan.refused": "O servidor não consegue aplicar este plano com segurança.", + "integrations.plan.refusal.notInstalled": "O cliente não está instalado. Instale-o, atualize esta página e visualize a alteração novamente.", + "integrations.plan.refusal.conflict": "As configurações gerenciadas entram em conflito com o arquivo atual. Revise o arquivo ou use Substituir quando essa ação estiver disponível.", + "integrations.plan.refusal.unsafe": "A configuração do cliente não pode ser alterada com segurança. Repare ou substitua o arquivo antes de tentar de novo.", + "integrations.plan.refusal.nonLoopback": "Este cliente exige um endereço de proxy loopback. Use acesso por localhost, um túnel ou um encaminhador local antes de tentar de novo.", + "integrations.plan.refusal.supersededStore": "Este cliente agora lê seus provedores de um arquivo que o opencodex não grava, então esta operação não mudaria nada do que ele carrega.", + "integrations.plan.refusal.driftRequiresConfirm": "O arquivo mudou depois deste ponto de rollback. Revise o plano com as diferenças antes de restaurar.", + "integrations.plan.refusal.snapshotExpired": "O backup deste ponto de rollback expirou, então ele não pode mais ser restaurado.", + "integrations.plan.refusal.writeFailed": "O servidor não conseguiu preparar uma gravação segura. Atualize o estado da integração antes de tentar de novo.", + "integrations.mutation.pending": "Aplicando a alteração confirmada…", + "integrations.bulk.breakage": "Cada cliente deixará de usar os modelos roteados por esta instância do opencodex.", + "integrations.bulk.undo": "Cada cliente mantém seu próprio backup e sua entrada de rollback.", + "integrations.bulk.confirm": "Desativar todos", + "integrations.semantics.opencode": "Apenas inicializações diretas do disco; a injeção de ambiente do ocx opencode tem precedência.", + "integrations.semantics.pi": "Vale para novas sessões.", + "integrations.semantics.omp": "Reinicie o OMP para carregar o catálogo.", + "integrations.semantics.hermes": "Vale para novas sessões.", + "integrations.semantics.openclaw": "Vale imediatamente para um gateway em execução.", + "integrations.semantics.kimi": "Reinicie ou execute /reload para aplicar (a v2 observa o arquivo).", + "integrations.semantics.gajae": "Vale para uma nova sessão ou ao abrir /model.", + "integrations.semantics.dsh": "O OpenCodex gerencia apenas llm-pi-ai.providers.opencodex em $DSH_HOME/settings.yaml. O DSH recarrega esse provedor a quente; seu modelo padrão e o deepseek-official permanecem inalterados. Atualmente somente loopback; nenhuma credencial real é gravada.", + "integrations.semantics.mcode": "Gerencia apenas custom_provider.opencodex. Seu modelo padrão e o login do MiniMax permanecem inalterados.", + "integrations.semantics.zcode": "Gerencia apenas provider.opencodex em ~/.zcode/v2/config.json. Seu login do Z.ai e os outros provedores permanecem inalterados. Reinicie o ZCode após as alterações.", + "integrations.semantics.prime": "Gerencia apenas providers.opencodex no models.json do Prime Agent — ~/.prime/agent, a menos que PRIME_AGENT_CODING_AGENT_DIR o redirecione. Seus outros provedores e substituições de modelo permanecem inalterados. Vale para novas sessões.", + "integrations.semantics.aside": "Gerencia apenas providers.opencodex no ~/.aside/u//models.json deste perfil. Seus outros provedores permanecem inalterados. Feche completamente e reabra o Aside após aplicar.", + "integrations.semantics.raycast": "Adiciona uma entrada de provedor OpenCodex ao providers.yaml do Raycast, para que todos os modelos roteados apareçam no seletor de modelos do Raycast AI. Requer Raycast Pro.", + "integrations.semantics.omo": "Gerencia apenas providers.opencodex no models.json do omo — ~/.omo/agent, a menos que OMO_CODING_AGENT_DIR, SENPI_CODING_AGENT_DIR ou PI_CODING_AGENT_DIR o redirecione. Seus outros provedores permanecem inalterados. Vale para novas sessões.", + "integrations.lazycodexRoles.title": "omo (Codex / LazyCodex) · Modelos das funções de agente do Codex", + "integrations.lazycodexRoles.hint": "Escolha o modelo que o Codex executa em cada função de agente. Ao salvar, só a linha do modelo no arquivo da função em $CODEX_HOME/agents é alterada, e a escolha é espelhada em omo.jsonc para o LazyCodex apenas quando esse arquivo puder ser reescrito com segurança.", + "integrations.lazycodexRoles.role": "Função", + "integrations.lazycodexRoles.current": "Modelo atual", + "integrations.lazycodexRoles.model": "Novo modelo", + "integrations.lazycodexRoles.modelFor": "Modelo para {role}", + "integrations.lazycodexRoles.none": "Nenhum modelo fixado", + "integrations.lazycodexRoles.choose": "Escolha um modelo", + "integrations.lazycodexRoles.empty": "Nenhuma função de agente do Codex foi encontrada.", + "integrations.lazycodexRoles.loadFailed": "Não foi possível carregar as funções de agente do Codex.", + "integrations.lazycodexRoles.saveFailed": "Não foi possível salvar o modelo de {role}.", + "integrations.lazycodexRoles.saved": "{role} agora usa {model}.", + "integrations.lazycodexRoles.omoAbsent": "{role} agora usa {model}. O omo.jsonc não foi encontrado, então só o arquivo da função foi alterado.", + "integrations.lazycodexRoles.omoComments": "{role} agora usa {model} no arquivo da função. O omo.jsonc não foi alterado porque salvar removeria seus comentários; defina codex.agents.{role}.model nele manualmente.", + "integrations.lazycodexRoles.omoInvalid": "{role} agora usa {model} no arquivo da função. O omo.jsonc não foi alterado porque não é um objeto JSON com um objeto codex.agents.", + "integrations.lazycodexRoles.omoFailed": "{role} agora usa {model} no arquivo da função, mas não foi possível gravar o omo.jsonc.", + "integrations.lazycodexRoles.retryMirror": "Tentar omo.jsonc novamente", + "integrations.lazycodexRoles.omoCommentsState": "O omo.jsonc contém comentários, então salvar aqui altera apenas os arquivos das funções. Edite codex.agents no omo.jsonc manualmente para mantê-lo sincronizado.", + "integrations.lazycodexRoles.auto.button": "Atribuir automaticamente", + "integrations.lazycodexRoles.auto.running": "Dimensionando funções…", + "integrations.lazycodexRoles.auto.hint": "Dimensiona cada função com seu modelo padrão e propõe o modelo suficiente mais barato para cada uma. Nada muda até você aplicar.", + "integrations.lazycodexRoles.auto.failed": "Não foi possível dimensionar as funções de agente do Codex.", + "integrations.lazycodexRoles.auto.title": "Modelos propostos", + "integrations.lazycodexRoles.auto.sizedWith": "Dimensionado com {model}", + "integrations.lazycodexRoles.auto.applyAll": "Aplicar todos", + "integrations.lazycodexRoles.auto.discard": "Descartar", + "integrations.lazycodexRoles.auto.sizingFailed": "A chamada de dimensionamento falhou: {error}", + "integrations.lazycodexRoles.auto.appliedCount": "{count} de {total} propostas aplicadas.", + "integrations.lazycodexRoles.auto.proposalFor": "Proposta para {role}", + "integrations.lazycodexRoles.auto.tierEffort": "Nível {tier}, esforço {effort}", + "integrations.lazycodexRoles.auto.tierFast": "Rápido", + "integrations.lazycodexRoles.auto.tierStandard": "Padrão", + "integrations.lazycodexRoles.auto.tierFrontier": "Frontier", + "integrations.lazycodexRoles.auto.effortGlance": "olhada rápida", + "integrations.lazycodexRoles.auto.effortMeasured": "moderado", + "integrations.lazycodexRoles.auto.effortThorough": "minucioso", + "integrations.lazycodexRoles.auto.effortExhaustive": "exaustivo", + "integrations.lazycodexRoles.auto.unsized": "Não dimensionado: {reason}", + "integrations.lazycodexRoles.auto.unassigned": "Sem modelo: {reason}", + "integrations.lazycodexRoles.auto.applied": "Aplicado", + "integrations.lazycodexRoles.auto.alreadySet": "Já definido", + "integrations.lazycodexRoles.auto.apply": "Aplicar", + "integrations.lazycodexRoles.auto.moveUp": "Suba de nível se: {text}", + "integrations.lazycodexRoles.auto.moveDown": "Desça de nível se: {text}", + "integrations.semantics.cline": "Gerencia o OpenCodex no providers.json e no models.json do Cline CLI. Pare o Cline antes de alterar ou sincronizar esses arquivos e reinicie depois. Desfazer restaura os dois originais. Seu provedor padrão permanece inalterado; selecione o OpenCodex no Cline.", + "integrations.semantics.kilo": "Gerencia apenas provider.opencodex na configuração global do Kilo — o primeiro arquivo existente entre kilo.jsonc, kilo.json, opencode.jsonc, opencode.json ou config.json em ~/.config/kilo (XDG_CONFIG_HOME realoca esse diretório; kilo.jsonc é criado quando nenhum existe). As demais chaves permanecem inalteradas. Aplicar reescreve o arquivo inteiro, então comentários e vírgulas finais não são preservados. Selecione opencodex/ no Kilo.", + "integrations.semantics.droid": "Adiciona modelos personalizados do OpenCodex ao settings.json do Factory Droid. Desativar remove apenas as linhas gerenciadas; desfazer restaura o arquivo salvo.", + "integrations.raycast.proRequired": "Custom Providers é um recurso do Raycast Pro. O arquivo será gravado, mas o Raycast o ignora até que uma assinatura Pro esteja ativa.", + "integrations.raycast.planUnknown": "Não foi possível determinar se o Raycast Pro está ativo; Custom Providers requer o Raycast Pro.", + "integrations.raycast.revealConfig": "Abra Raycast → Settings → AI e clique uma vez em Reveal Providers Config para que a pasta de provedores exista.", + "codexAuth.mainAccount": "Conta principal", + "codexAuth.logLabel": "Rótulo no log", + "codexAuth.codexApp": "App Codex", + "codexAuth.moreActions": "Mostrar mais ações", + "codexAuth.copyId": "Copiar ID da conta", + "codexAuth.appLogin": "Login do app", + "codexAuth.accountPool": "Pool de contas", + "codexAuth.accountModeTitle": "Modo de conta OpenAI", + "codexAuth.accountModePool": "Modo Pool", + "codexAuth.accountModePoolDesc": "O login principal e as contas adicionadas elegíveis se alternam aqui.", + "codexAuth.accountModeDirect": "Modo direto", + "codexAuth.accountModeDirectDesc": "As requisições usam apenas o login principal; as contas adicionadas continuam armazenadas para o modo Pool.", + "codexAuth.openaiMissing": "O provedor OpenAI integrado não está configurado.", + "codexAuth.openaiDisabled": "O provedor OpenAI integrado está desativado.", + "codexAuth.openaiUnavailableDesc": "Suas contas OpenAI continuam disponíveis. Ative o provedor para rotear as requisições do Codex.", + "codexAuth.enableOpenai": "Ativar OpenAI", + "codexAuth.enablingOpenai": "Ativando...", + "codexAuth.enableOpenaiFailed": "Falha ao ativar o provedor OpenAI.", + "codexAuth.openaiPresetLoadFailed": "Falha ao carregar o preset do provedor OpenAI.", + "codexAuth.openaiPresetUnavailable": "O preset do provedor OpenAI não está disponível.", + "codexAuth.openProviders": "Abrir Provedores", + "codexAuth.add": "Adicionar", + "codexAuth.credits": "Créditos", + "codexAuth.creditsRemaining": "restantes", + "codexAuth.creditsUnlimited": "Ilimitado", + "codexAuth.creditsOverage": "Limite de excedente atingido", + "codexAuth.creditsApprox": "Mensagens aprox.: local {local} · nuvem {cloud}", + "codexAuth.creditsToggle": "Créditos do Codex", + "codexAuth.creditsToggleHint": "Mostra os créditos restantes do Codex nos cartões das contas. Oculto por padrão.", + "codexAuth.creditsShown": "Créditos do Codex visíveis", + "codexAuth.creditsHidden": "Créditos do Codex ocultos", + "codexAuth.creditsToggleFailed": "Não foi possível alterar a configuração de créditos do Codex", + "codexAuth.refreshQuota": "Atualizar cotas", + "codexAuth.ultraFastTitle": "Nível de serviço Ultra Fast", + "codexAuth.mainHardLockTitle": "Bloquear conta principal (5h {short}%, longa {long}%)", + "codexAuth.mainHardLockDesc": "Bloqueia em {short}% na janela de 5h ou em {long}% na janela semanal (mensal, para contas apenas mensais). Desbloqueia quando todas as janelas bloqueantes ficam abaixo do limite; a proteção continua ativa. Ativada por padrão.", + "codexAuth.mainHardLockConfirmTitle": "Ativar a proteção da conta principal (5h {short}%, longa {long}%)?", + "codexAuth.mainHardLockConfirmBody": "Enquanto estiver bloqueada, a conta principal não pode usar a Luna Reserve. Manter o uso normal abaixo do esgotamento pode impedir a ativação da Reserve. As contas adicionadas e os outros provedores continuam disponíveis. Requisições em andamento, credenciais de keyring sem correspondência e tráfego fora deste proxy não são protegidos.", + "codexAuth.mainHardLockConfirm": "Ativar proteção", + "codexAuth.mainHardLockEnabled": "A proteção está ativada (5h {short}%, longa {long}%).", + "codexAuth.mainHardLockDisabled": "A proteção da conta principal está desativada. Os limites das outras contas continuam valendo.", + "codexAuth.mainHardLockLoadFailed": "Não foi possível carregar esta configuração. Tente de novo para verificar o estado atual.", + "codexAuth.mainHardLockSaveFailed": "Não foi possível confirmar o salvamento. Recarregue a configuração antes de tentar de novo.", + "codexAuth.mainHardLockRefreshFailed": "Configuração salva, mas não foi possível atualizar o status das contas. Tente novamente.", + "codexAuth.mainHardLockBlocked": "Bloqueada pela proteção da conta principal (5h {short}%, longa {long}%)", + "codexAuth.mainHardLockUnknown": "Proteção ativa · uso desconhecido", + "codexAuth.mainHardLockMonitoring": "Proteção ativa · monitorando", + "codexAuth.mainExternalUsageWarning": "Possível uso fora do opencodex. O bloqueio pode não impedir o esgotamento.", + "codexAuth.mainHardLockManage": "Ver configuração de proteção", + "codexAuth.ultraFastDesc": "Impede que um nível de serviço ultrarrápido configurado por você seja removido quando o catálogo é regenerado e o nomeia nos logs de requisição. Não adiciona o Ultra Fast ao seletor de modelos: o upstream anuncia apenas o Fast, então uma linha no seletor ofereceria uma velocidade que a conexão não consegue entregar.", + "codexAuth.ultraFastLoadFailed": "Não foi possível ler a configuração do Ultra Fast.", + "codexAuth.ultraFastEnabled": "Nível Ultra Fast ativado", + "codexAuth.ultraFastDisabled": "Nível Ultra Fast desativado", + "codexAuth.ultraFastFailed": "Não foi possível alterar a configuração do Ultra Fast", + "codexAuth.refreshingQuota": "Atualizando...", + "codexAuth.quotaRefreshed": "Cotas atualizadas", + "codexAuth.quotaRefreshFailed": "Falha ao atualizar as cotas", + "codexAuth.pauseExhausted": "Pausar esgotadas", + "codexAuth.pausingExhausted": "Verificando cotas...", + "codexAuth.pauseExhaustedSucceeded": "Contas no limite pausadas: {count}", + "codexAuth.pauseExhaustedNone": "Nenhuma conta tem 100% de uso confirmado.", + "codexAuth.pauseExhaustedFailed": "Falha ao verificar e pausar as contas esgotadas.", + "codexAuth.noPool": "Nenhuma conta do pool foi adicionada ainda.", + "codexAuth.pause": "Pausar", + "codexAuth.resume": "Retomar", + "codexAuth.paused": "PAUSADA", + "codexAuth.planExcluded": "Não selecionada automaticamente", + "codexAuth.planExcludedHint": "O plano {plan} está excluído da seleção automática. As rotas explícitas por conta continuam disponíveis.", + "codexAuth.pauseSucceeded": "{email} está pausada", + "codexAuth.resumeSucceeded": "{email} voltou a ficar disponível para o pool", + "codexAuth.pauseFailed": "Não foi possível pausar {email}. Nada foi alterado.", + "codexAuth.resumeFailed": "Não foi possível retomar {email}. Nada foi alterado.", + "codexAuth.pausedHint": "Excluída da troca automática, das novas tentativas, da recuperação de cooldown e da seleção manual até ser retomada.", + "codexAuth.creditsAfterLimit": "Usar créditos após o limite", + "codexAuth.creditsAfterLimitHint": "Desativado por padrão: uma conta cuja janela de uso chega a 100% é retirada de uso até a janela reiniciar, para que seus créditos do ChatGPT não sejam gastos. Ative em uma conta para que ela continue trabalhando com seus créditos. Contas novas começam desativadas.", + "codexAuth.creditsAfterLimitAria": "Usar créditos após o limite de uso para {email}", + "codexAuth.creditsAfterLimitMainHint": "O bloqueio rígido da conta principal (ativado por padrão) ainda a interrompe primeiro. Desative o bloqueio para que a conta principal gaste créditos.", + "codexAuth.creditSpend": "Usar créditos", + "codexAuth.creditSpendAria": "Usar créditos do ChatGPT após o limite de uso", + "codexAuth.creditsOn": "Usa créditos", + "codexAuth.creditsOnSucceeded": "{email} pode usar créditos após o limite de uso.", + "codexAuth.creditsOffSucceeded": "{email} será retirada de uso ao atingir o limite, e seus créditos serão preservados.", + "codexAuth.creditsUpdateFailed": "Não foi possível atualizar a configuração de créditos de {email}. Nada foi alterado.", + "codexAuth.creditsAllOnSucceeded": "Agora todas as contas podem usar créditos após o limite de uso.", + "codexAuth.creditsAllOffSucceeded": "Nenhuma conta gastará créditos. As contas são retiradas de uso ao chegar a 100%.", + "codexAuth.creditsAllUpdateFailed": "Não foi possível atualizar a configuração de créditos. Nada foi alterado.", + "codexAuth.pinned": "FIXADA", + "codexAuth.pinnedHint": "Você selecionou esta conta manualmente, então uma ordem de seleção mais alta não passará por cima dela. A fixação dura até esta conta ser esgotada, você selecionar outra ou alterar qualquer ordem de seleção.", + "codexAuth.fiveHour": "5h", + "codexAuth.weekly": "Semana", + "codexAuth.quotaAutoRefresh": "Ativação automática da janela", + "codexAuth.quotaAutoRefreshHint": "Envia uma requisição mínima de aquecimento do Codex, sem armazenamento, quando esta janela de cota é reiniciada.", + "codexAuth.quotaAutoRefreshUpdated": "Ativação automática da janela atualizada.", + "codexAuth.quotaAutoRefreshFailed": "Não foi possível atualizar a ativação automática da janela.", + "codexAuth.monthly": "30d", + "codexAuth.resets": "reinicia", + "codexAuth.today": "Hoje", + "codexAuth.current": "ATUAL", + "codexAuth.nextSession": "SELECIONADA", + "codexAuth.poolPrepared": "PREPARADA PARA O POOL", + "codexAuth.preparePoolTitle": "Preparar esta conta para o modo Pool?", + "codexAuth.preparePoolDesc": "As requisições diretas continuam usando o login principal. Esta conta passa a ser a seleção preparada do Pool quando o modo Pool for ativado.", + "codexAuth.prepareForPool": "Preparar para o Pool", + "codexAuth.poolPreparedToast": "{email} está preparada para o modo Pool", + "codexAuth.switchTitle": "Trocar a conta ativa?", + "codexAuth.switchDesc": "Vale imediatamente. As threads com afinidade de conta e as requisições já em andamento mantêm a conta capturada; as requisições novas ou sem vínculo usam a faixa de ordem da conta selecionada, e as contas com a mesma ordem de seleção continuam se revezando.", + "codexAuth.cacheWarning": "O cache de prompt é reiniciado ao trocar de conta. A nova sessão começa com o cache vazio.", + "codexAuth.switchExceedsThresholdWarning": "O uso desta conta atinge ou excede o limite de troca ({threshold}%). A seleção fixada será liberada se não houver folga de cota.", + "codexAuth.setAsNext": "Usar esta conta em seguida", + "codexAuth.cancel": "Cancelar", + "codexAuth.switchBack": "Voltar para a conta principal?", + "codexAuth.switchBackDesc": "Vale imediatamente. As threads com afinidade de conta e as requisições já em andamento mantêm a conta capturada; as requisições novas ou sem vínculo usam a faixa de ordem da conta do login do app, e as contas com a mesma ordem de seleção continuam se revezando.", + "codexAuth.autoSwitch": "Troca proativa por uso", + "codexAuth.autoSwitchQuotaDesc": "Cota: com {threshold}% de uso ou mais, a próxima requisição sem vínculo pode ir para uma conta elegível de menor uso. As tarefas vinculadas mantêm a afinidade por padrão e só mudam quando a conta não consegue atender, e apenas para uma conta com folga real de cota; Go/Free usam somente 30d.", + "codexAuth.autoSwitchQuotaOffDesc": "A troca proativa por uso está desativada. A atribuição de tarefas novas/sem vínculo e a recuperação de falhas continuam valendo.", + "codexAuth.autoSwitchRoundRobinDesc": "A atribuição round-robin não usa este limite; continua alternando as tarefas novas/sem vínculo.", + "codexAuth.autoSwitchFillFirstDesc": "Fill-first: {threshold}% é o ponto de esgotamento para tarefas novas/sem vínculo; as tarefas vinculadas saudáveis mantêm a conta.", + "codexAuth.autoSwitchFillFirstOffDesc": "O fill-first não tem ponto de esgotamento por uso para tarefas novas/sem vínculo; cooldown, reautenticação e recuperação de falhas ainda podem mudar o roteamento.", + "codexAuth.failureRecoveryNote": "A recuperação de falhas é separada: uma requisição rejeitada antes de qualquer saída com 429/402, cooldown, reautenticação, exclusão ou failover transitório configurado pode selecionar outra conta elegível.", + "codexAuth.autoSwitchThreshold": "Limite de uso", + "codexAuth.autoSwitchThresholdAria": "Limite de uso, em porcentagem", + "codexAuth.autoSwitchThresholdInc": "Aumentar o limite de uso", + "codexAuth.autoSwitchThresholdDec": "Diminuir o limite de uso", + "codexAuth.autoSwitchLoadFailed": "Não foi possível carregar a configuração de troca por uso.", + "codexAuth.autoSwitchThresholdInvalid": "Digite um número inteiro de 1 a 100", + "codexAuth.autoSwitchUpdated": "Troca proativa por uso atualizada", + "codexAuth.autoSwitchUpdateFailed": "Não foi possível confirmar a atualização da troca por uso. O último valor confirmado é exibido.", + "codexAuth.requestUserInput": "Pedir entrada no modo Default", + "codexAuth.requestUserInputDesc": "Permite que o Codex pause uma sessão no modo Default e faça perguntas a você com a ferramenta request_user_input.", + "codexAuth.requestUserInputUpdated": "Feature flag atualizada - vale para novas sessões.", + "codexAuth.requestUserInputUpdatedRestart": "Feature flag atualizada - vale para novas sessões. Reinicie o app Codex para aplicá-la.", + "codexAuth.requestUserInputUpdateFailed": "Não foi possível atualizar a feature flag. Nada foi alterado.", + "codexAuth.requestUserInputLoadFailed": "Não foi possível ler a feature flag do config.toml.", + "codexAuth.accountPickerTitle": "Direcionar uma conta específica do Codex pelo seletor de modelos", + "codexAuth.accountPickerOffDesc": "Quando ativado, as linhas comuns de GPT no seletor são substituídas por uma entrada por seletor de conta, para que você escolha a conta exata de uma conversa sem precisar sair da sessão. Desativar não remove nenhuma conta.", + "codexAuth.accountPickerOnDesc": "Cada seletor é um rótulo público de uma conta armazenada. Escolhê-lo trava a conversa na conta mapeada: ela nunca alterna nem recorre a outra, e não muda a conta ativa do Pool.", + "codexAuth.accountPickerCompatibility": "O login integrado do app Codex tem seu próprio seletor; os mapas gerados normalmente o chamam de main e usam um sufixo à prova de colisão, como main-2, quando necessário. As contas adicionadas recebem rótulos estáveis que preservam a privacidade, enquanto os rótulos personalizados de seletor permanecem inalterados. As conversas existentes e as seleções de modelo salvas continuam roteando. Desativar oculta as entradas geradas, mas preserva os seletores e as rotas exatas. Os IDs simples de modelos GPT mantêm o comportamento de Pool ou Direct.", + "codexAuth.accountPickerUpdated": "Direcionamento de conta atualizado.", + "codexAuth.accountPickerUpdateFailed": "Não foi possível atualizar o direcionamento de conta. A última configuração confirmada é exibida.", + "codexAuth.accountPickerLoadFailed": "Não foi possível carregar a configuração de direcionamento de conta.", + "codexAuth.accountPickerRefreshFailed": "Não foi possível atualizar esta configuração. O último valor confirmado continua sendo exibido.", + "codexAuth.advancedSettings": "Configurações avançadas", + "codexAuth.advancedSettingsAria": "Mostrar ou ocultar as configurações avançadas do Codex Auth", + "codexAuth.catalogRefreshPending": "A alteração foi salva, mas a atualização do catálogo de modelos do Codex está pendente. Execute ocx sync para tentar de novo.", + "anthropicPool.title": "Pool de contas Claude (experimental)", + "anthropicPool.enabledDesc": "Sessões fixas, e as novas sessões preferem contas com uso abaixo de {threshold}% ({window}).", + "anthropicPool.enabledNoProactiveDesc": "Os limites de uso não movem uma sessão saudável existente nem uma conta ativa saudável. Quando a conta ativa fica indisponível, e durante a recuperação de recusa, é escolhida a conta com menor uso ({window}).", + "anthropicPool.enabledFillFirstDesc": "Sessões fixas. As novas sessões permanecem na conta ativa até ela atingir {threshold}% ({window}), entrar em cooldown ou precisar de novo login; depois passam para a próxima conta, em ordem. A recuperação de recusa também usa a próxima conta, em ordem.", + "anthropicPool.enabledFillFirstNoThresholdDesc": "Sessões fixas. As novas sessões permanecem na conta ativa até ela entrar em cooldown ou precisar de novo login; depois passam para a próxima conta, em ordem. A recuperação de recusa também usa a próxima conta, em ordem.", + "anthropicPool.enabledRoundRobinDesc": "Sessões fixas. As novas sessões e a recuperação de recusa se revezam entre as contas elegíveis; uso, limite e janela de cota não são considerados.", + "anthropicPool.disabledDesc": "A seleção proativa de contas está desativada. O OpenCodex usa a conta selecionada e ainda pode trocar após uma resposta de limite de taxa (429) ou uma recusa de conta classificada, quando houver outra conta elegível conectada.", + "anthropicPool.experimentalWarning": "Use apenas assinaturas que você possua ou esteja autorizado a usar. O pooling automatizado de contas não é endossado pela Anthropic e continua experimental. Use o cliente Claude Code genuíno com uma pessoa supervisionando a sessão e verifique os termos vigentes da Anthropic. As contas podem compartilhar a cota de uma organização, então adicionar uma conta pode não aumentar a capacidade.", + "anthropicPool.detailsSummary": "Como funciona a seleção de contas", + "anthropicPool.detailsEnabling": "Ativar o pool mantém cada sessão em sua conta enquanto ela estiver saudável e permite que a estratégia selecionada escolha as contas das novas sessões e após uma recusa.", + "anthropicPool.detailsFailover": "Desativá-lo não desativa o failover: após um 429 ou uma recusa de conta classificada, uma requisição ainda pode passar para outra conta conectada. Pause uma conta para mantê-la fora do failover.", + "anthropicPool.detailsActivity": "O OpenCodex não envia requisições keep-warm. Por padrão, não renova tokens do Claude nem consulta o uso em segundo plano; o uso só é lido quando o painel, o app da barra de menus ou um comando ocx o solicita.", + "anthropicPool.detailsGuide": "Guia do pool de contas", + "anthropicPool.needTwoAccounts": "Adicione pelo menos duas contas Claude OAuth antes de ativar o pool.", + "anthropicPool.threshold": "Limite de uso para novas sessões", + "anthropicPool.thresholdAria": "Limite de uso para novas sessões, em porcentagem", + "anthropicPool.thresholdHelp": "É uma preferência para a seleção de contas, não um limite rígido de uso ou de cobrança. 0 desativa a troca proativa por este limite. Padrão: 80%.", + "anthropicPool.thresholdInvalid": "Digite um número inteiro de 0 a 100", + "anthropicPool.loadFailed": "Não foi possível carregar as configurações do pool Claude.", + "anthropicPool.saveFailed": "Não foi possível salvar as configurações do pool Claude.", + "anthropicPool.on": "Ligado", + "anthropicPool.off": "Desligado", + "accountPool.strategy": "Estratégia de rotação", + "accountPool.strategyDesc": "Como o OpenCodex atribui uma conta a uma tarefa nova/sem vínculo.", + "accountPool.strategyResetFirst": "Reinício mais próximo primeiro", + "accountPool.strategyHintResetFirst": "Prefere o reinício de 5 horas ou semanal futuro mais próximo entre as contas abaixo do limite de uso. As tarefas vinculadas seguem a política de afinidade configurada. Cotas independentes por modelo usam a ordenação por cota.", + "accountPool.strategyQuota": "Cota", + "accountPool.strategyRoundRobin": "Round-robin", + "accountPool.strategyFillFirst": "Fill-first", + "accountPool.strategyHintQuota": "A cota revincula uma tarefa existente no limite de uso apenas quando `pool.cacheAffinity` está desativado (por padrão, ele vem ativado). Caso contrário, as tarefas vinculadas permanecem até a conta não conseguir atender, e então só vão para uma conta com folga real de cota.", + "accountPool.strategyHintRoundRobin": "O round-robin alterna apenas as tarefas sem vínculo ativo; o limite de uso não altera a rotação normal.", + "accountPool.strategyHintFillFirst": "O fill-first usa o limite como ponto de esgotamento para tarefas sem vínculo; as tarefas vinculadas saudáveis mantêm a afinidade.", + "accountPool.unboundDefinition": "Tarefa nova/sem vínculo é uma requisição sem vínculo atual com uma conta; uma tarefa visível existente pode ficar sem vínculo após um reset do proxy ou da afinidade.", + "accountPool.stickyLimit": "Atribuições novas/sem vínculo antes de alternar", + "accountPool.stickyLimitAria": "Atribuições novas/sem vínculo antes de alternar", + "accountPool.stickyLimitInc": "Aumentar o limite de fixação", + "accountPool.stickyLimitDec": "Diminuir o limite de fixação", + "accountPool.stickyLimitHelp": "Mantém a conta selecionada por esta quantidade de atribuições de tarefas novas/sem vínculo antes de avançar; o contador aumenta quando a tarefa é vinculada, não após o sucesso no upstream.", + "accountPool.stickyLimitInvalid": "Digite um número inteiro de 1 a 100", + "accountPool.strategyLoadFailed": "Não foi possível carregar a estratégia de rotação.", + "accountPool.strategyUpdateFailed": "Não foi possível salvar a estratégia de rotação.", + "accountPool.switchAtThreshold": "trocar em {threshold}%", + "accountPool.drainAtThreshold": "esgotar em {threshold}%", + "accountPool.resetBelowThreshold": "reinício mais próximo abaixo de {threshold}%", + "accountPool.thresholdNotUsed": "limite não utilizado", + "accountPool.proactiveSwitchingOff": "troca proativa desativada", + "accountPool.quotaWindow": "Janela de cota", + "accountPool.quotaWindowDesc": "Qual barra de uso em cache controla a seleção de novas sessões baseada em cota, as verificações de limite do fill-first e as substituições elegíveis por 429.", + "accountPool.quotaWindowFiveHour": "Barra de 5 horas", + "accountPool.quotaWindowWeekly": "Barra semanal", + "accountPool.quotaWindowMaxUtilization": "Barra mais alta", + "accountPool.quotaWindowHint": "A semanal ignora as contas cuja barra de 5 horas está esgotada enquanto houver outra conta elegível, mas recorre a elas quando não houver. Empates na semanal preferem o menor uso de 5 horas; as barras semanais por conta só são conhecidas depois que a página Provedores as consultar.", + "accountPool.quotaWindowInert": "Só a cota — ou o fill-first com limite acima de 0 — pontua uma barra de uso, então esta configuração não muda nada na estratégia de rotação atual.", + "accountPool.priority": "Ordem de seleção", + "accountPool.priorityAria": "Ordem de seleção desta conta", + "accountPool.priorityHint": "Números maiores são usados primeiro. O pool passa para um número menor apenas quando todas as contas acima dele estiverem esgotadas ou indisponíveis.", + "accountPool.priorityFirst": "Primeira", + "accountPool.priorityEarlier": "Antes", + "accountPool.priorityNormal": "Normal", + "accountPool.priorityLater": "Depois", + "accountPool.priorityLast": "Última", + "accountPool.priorityOption": "{name} ({value})", + "accountPool.priorityCustom": "Personalizada", + "accountPool.priorityUpdated": "Ordem de seleção atualizada para {email}", + "accountPool.priorityUpdateFailed": "Não foi possível salvar a ordem de seleção de {email}. O último valor confirmado é exibido.", + "accountPool.autoSwitchThreshold": "Limite personalizado da conta", + "accountPool.autoSwitchThresholdAria": "Limite de uso de {email}", + "accountPool.autoSwitchOverrideAria": "Substituir o limite de uso global de {email}", + "accountPool.autoSwitchHint": "Substitui o limite global desta conta. 0 desativa a troca proativa por uso a partir desta conta.", + "accountPool.autoSwitchUpdated": "Limite de uso atualizado para {email}", + "accountPool.autoSwitchUpdateFailed": "Não foi possível salvar o limite de uso de {email}. O último valor confirmado é exibido.", + "codexAuth.switched": "{email} está selecionada para a próxima requisição", + "codexAuth.loadFailed": "Não foi possível carregar as configurações da conta Codex.", + "codexAuth.accountsRefreshFailed": "A última atualização das contas falhou. As contas abaixo são as últimas confirmadas.", + "codexAuth.switchFailed": "Não foi possível trocar de conta. Sua seleção anterior foi mantida.", + "codexAuth.removeConfirm": "Remover {id}?", + "codexAuth.removeFailed": "Não foi possível remover a conta. Nada foi alterado.", + "codexAuth.addTitle": "Adicionar conta Codex", + "codexAuth.addIdLabel": "ID da conta (slug)", + "codexAuth.addIdPlaceholder": "codex-work, codex-alt, team...", + "codexAuth.resetCreditsAria": "{count} crédito(s) de reset", + "codexAuth.addJsonLabel": "Conteúdo do auth.json", + "codexAuth.addHelp": "Copie do ~/.codex/auth.json de outra máquina ou use codex-auth export.", + "codexAuth.importBtn": "Importar", + "codexAuth.importInvalidJson": "JSON inválido", + "codexAuth.importMissingTokens": "access_token ou refresh_token ausente no JSON", + "codexAuth.importMissingId": "O ID da conta é obrigatório", + "codexAuth.accountAdded": "Conta adicionada ao pool", + "codexAuth.addPickDesc": "Faça login com outra conta do ChatGPT para adicioná-la ao pool.", + "codexAuth.oauthLogin": "Login OAuth", + "codexAuth.oauthDesc": "Abre o login do ChatGPT no navegador", + "codexAuth.deviceLogin": "Login por código de dispositivo", + "codexAuth.deviceDesc": "Para proxy headless ou remoto: digite um código curto em outro dispositivo", + "codexAuth.importAuthJson": "Importar auth.json", + "codexAuth.importAuthJsonDesc": "De outra instalação do Codex ou de um codex-auth export", + "codexAuth.back": "Voltar", + "codexAuth.oauthAlreadyInProgress": "Já há um login em andamento. Conclua-o no navegador.", + "codexAuth.oauthWaiting": "Aguardando a conclusão do login do ChatGPT no navegador...", + "codexAuth.oauthSubmittingCode": "Enviando código…", + "codexAuth.oauthCodeSubmitted": "Código enviado — aguardando a conclusão do login…", + "codexAuth.oauthStatusRetrying": "Erro de rede ou de proxy ao verificar o status do login — tentando novamente…", + "codexAuth.oauthCancelled": "O login foi cancelado.", + "codexAuth.loginFailed": "Falha no login", + "codexAuth.needsReauth": "Refazer login", + "codexAuth.reauthenticate": "Reautenticar", + "codexAuth.tokenExpired": "Token expirado — reautentique esta conta", + "codexAuth.mainTokenExpired": "Token expirado — refaça o login com um código de dispositivo abaixo ou pelo login do Codex App", + "codexAuth.mainReauthSucceeded": "Login realizado", + "codexAuth.mainReauthFailed": "Falha ao refazer o login", + "codexAuth.mainReauthCancel": "Cancelar", + "codexAuth.mainReauthCode": "Código", + "codexAuth.mainReauthOpen": "Abrir", + "codexAuth.mainReauthPending": "Aguardando o login…", + "codexAuth.mainReauthDevice": "Refazer login com código de dispositivo", + "codexAuth.emailCollision": "Esta conta corresponde ao seu login principal do Codex. Use outra conta.", + "codexAuth.resetCreditsTitle": "Créditos de reset", + "codexAuth.resetCreditsAvailable": "Você tem {count} crédito(s) de reset disponível(is).", + "codexAuth.resetCreditsDesc": "Cada crédito zera imediatamente seus limites de uso por hora e por semana.", + "codexAuth.noResetCredits": "Você não tem créditos de reset.", + "codexAuth.earnCreditsHint": "Os créditos são ganhos mensalmente e pelo programa de indicação.", + "codexAuth.creditsExpireNote": "Os créditos expiram 30 dias após serem ganhos.", + "codexAuth.useOneCredit": "Usar 1 crédito", + "codexAuth.confirmResetTitle": "Usar crédito de reset?", + "codexAuth.confirmResetDesc": "Isso zerará imediatamente seus limites de taxa atuais. Restam {count} crédito(s).", + "codexAuth.irreversible": "Esta ação não pode ser desfeita.", + "codexAuth.useCredit": "Usar crédito", + "codexAuth.redeeming": "Zerando...", + "codexAuth.resetSuccess": "Limites de taxa zerados! Restam {remaining} crédito(s).", + "codexAuth.resetSuccessGeneric": "Limites de taxa zerados!", + "codexAuth.resetAlreadyRedeemed": "Este crédito já foi resgatado. Créditos inalterados.", + "codexAuth.resetNothingToReset": "Nenhuma janela de limite de taxa precisa ser zerada no momento.", + "codexAuth.resetNoCredit": "Nenhum crédito de reset disponível.", + "codexAuth.resetError": "Falha ao resgatar o crédito de reset. Tente novamente.", + "codexAuth.fifoNote": "O crédito mais antigo é usado primeiro.", + "codexAuth.confirmWhichCredit": "O crédito de {date} será usado.", + "codexAuth.creditNext": "Próximo a usar", + "codexAuth.creditLabel": "Crédito nº {n}", + "codexAuth.creditNextBadge": "PRÓXIMO", + "codexAuth.creditGranted": "Concedido em {date}", + "codexAuth.creditExpires": "Expira em {date} ({days}d restantes)", + "grokCoupon.badgeAria": "{count} cupom(ns) de reset do Grok", + "grokCoupon.badgeErrorAria": "Não foi possível ler os cupons de reset do Grok", + "grokCoupon.title": "Cupons de reset do Grok", + "grokCoupon.available": "Esta conta tem {count} cupom(ns) de reset.", + "grokCoupon.desc": "Um cupom zera imediatamente a janela de uso do Grok desta conta.", + "grokCoupon.none": "Esta conta não tem cupons de reset.", + "grokCoupon.loadFailed": "Não foi possível ler os cupons de reset desta conta.", + "grokCoupon.loadFailedAuth": "Faça login novamente nesta conta xAI para ler os cupons.", + "grokCoupon.retry": "Tentar novamente", + "grokCoupon.couponLabel": "Cupom nº {n}", + "grokCoupon.couponNext": "Próximo a usar", + "grokCoupon.couponNextBadge": "PRÓXIMO", + "grokCoupon.validFrom": "Válido a partir de {date}", + "grokCoupon.expires": "Expira em {date} ({days}d restantes)", + "grokCoupon.useOne": "Usar 1 cupom", + "grokCoupon.fifoNote": "O cupom mais próximo do vencimento é usado primeiro.", + "grokCoupon.confirmTitle": "Usar cupom de reset?", + "grokCoupon.confirmDesc": "Isso zera agora a janela de uso do Grok desta conta. Você tem {count} cupom(ns).", + "grokCoupon.confirmWhich": "O cupom que expira em {date} será usado.", + "grokCoupon.irreversible": "Esta ação não pode ser desfeita.", + "grokCoupon.redeem": "Usar cupom", + "grokCoupon.redeeming": "Resgatando…", + "grokCoupon.redeemSuccess": "Cupom resgatado. Restam {count} cupom(ns).", + "grokCoupon.redeemReplayed": "Este resgate já foi concluído antes. Nada foi consumido novamente.", + "grokCoupon.redeemFailed": "Falha no resgate. Confira a lista de cupons antes de tentar novamente.", + "grokCoupon.authFailed": "Faça login novamente nesta conta xAI para usar os cupons.", + "grokCoupon.noAccount": "Nenhuma conta xAI está disponível para este resgate.", + "grokCoupon.noneAvailable": "Não há cupons de reset disponíveis para resgatar.", + "grokCoupon.identityMismatch": "Esse resgate pertence a outra conta. Feche este diálogo e comece de novo.", + "grokCoupon.networkError": "Não foi possível alcançar o proxy.", + "grokCoupon.capacity": "O registro de resgates está cheio no momento. Tente novamente em instantes.", + "grokCoupon.noOperationId": "Este navegador não consegue gerar um ID de resgate, então o resgate está desativado aqui.", + "grokCoupon.unknownOutcome": "O resgate não respondeu a tempo, então seu resultado é desconhecido. Releia a conta para ver se o cupom foi gasto.", + "grokCoupon.checkResult": "Reler conta", + "grokCoupon.checking": "Relendo…", + "grokCoupon.consumedElsewhere": "O cupom não está mais nesta conta, então o resgate foi concluído.", + "grokCoupon.unresolved": "O cupom ainda aparece na lista. O resgate pode ainda estar em andamento no upstream — releia em um minuto antes de gastar outro cupom.", + "anthropicGrant.badgeAria": "{count} reset(s) de uso do Claude", + "anthropicGrant.badgeErrorAria": "Não foi possível ler os resets de uso do Claude", + "anthropicGrant.title": "Resets de uso do Claude", + "anthropicGrant.available": "Esta conta tem {count} reset(s) de uso.", + "anthropicGrant.desc": "Um reset recompõe imediatamente os limites de 5 horas e semanais do Claude desta conta. O dia do reset semanal permanece o mesmo.", + "anthropicGrant.none": "Esta conta não tem resets de uso.", + "anthropicGrant.ineligible": "Esta conta não é elegível para resets de uso no momento.", + "anthropicGrant.loadFailed": "Não foi possível ler os resets de uso desta conta.", + "anthropicGrant.loadFailedAuth": "Faça login novamente nesta conta Anthropic para ler os resets.", + "anthropicGrant.retry": "Tentar novamente", + "anthropicGrant.resetsLeft": "{left} de {total} restante(s)", + "anthropicGrant.validFrom": "Válido a partir de {date}", + "anthropicGrant.expires": "Expira em {date} ({days}d restantes)", + "anthropicGrant.windowFiveHour": "5 horas {percent}%", + "anthropicGrant.windowWeekly": "Semanal {percent}%", + "anthropicGrant.windowOverage": "Extra {percent}%", + "anthropicGrant.paused": "Pausado", + "anthropicGrant.notUsable": "Ainda não utilizável", + "anthropicGrant.requiresLimit": "Utilizável quando esta conta atingir um limite", + "anthropicGrant.useOne": "Usar 1 reset", + "anthropicGrant.confirmTitle": "Zerar os limites de uso agora?", + "anthropicGrant.confirmDesc": "Isso recompõe agora os limites de uso do Claude desta conta e consome um reset.", + "anthropicGrant.confirmWhich": "Usa: {label}", + "anthropicGrant.irreversible": "Esta ação não pode ser desfeita.", + "anthropicGrant.redeem": "Zerar limites", + "anthropicGrant.redeeming": "Zerando…", + "anthropicGrant.resultReset": "Limites zerados. Restam {count} reset(s).", + "anthropicGrant.resultAlreadyUsed": "Este reset já foi usado.", + "anthropicGrant.resultNotLimited": "Esta conta não está em um limite, então nada foi usado.", + "anthropicGrant.resultCooldown": "Os resets estão em período de espera para esta conta. Nada foi usado.", + "anthropicGrant.resultIneligible": "Esta conta não é mais elegível. Nada foi usado.", + "anthropicGrant.resultUnavailable": "Os resets estão indisponíveis no momento. Nada foi usado.", + "anthropicGrant.resultRateLimited": "Requisições demais. Nada foi usado; tente novamente em um minuto.", + "anthropicGrant.resultAuthError": "Faça login novamente nesta conta Anthropic. Nada foi usado.", + "anthropicGrant.replayed": "Este reset já foi concluído antes. Nada foi usado novamente.", + "anthropicGrant.notUsableNow": "Este reset não pode ser usado no momento. Releia a conta.", + "anthropicGrant.inFlight": "Este reset ainda está sendo processado. Aguarde um instante e releia a conta.", + "anthropicGrant.unresolvedPrior": "Uma tentativa anterior deste reset ainda não foi confirmada. Repita essa tentativa em vez de iniciar uma nova.", + "anthropicGrant.expired": "A tentativa anterior não pôde ser confirmada a tempo. Releia a conta antes de usar um reset.", + "anthropicGrant.identityMismatch": "Esse reset pertence a outra conta. Feche este diálogo e comece de novo.", + "anthropicGrant.sessionRequired": "Abra o painel pelo login próprio dele para usar os resets.", + "anthropicGrant.journalBusy": "O registro de resets está ocupado ou indisponível. Nada foi usado; tente novamente em instantes.", + "anthropicGrant.failed": "A requisição de reset falhou. Verifique a conta antes de tentar novamente.", + "anthropicGrant.noOperationId": "Este navegador não consegue gerar um ID de requisição, então os resets estão desativados aqui.", + "anthropicGrant.unknownOutcome": "O reset não respondeu a tempo, então seu resultado é desconhecido. Tentar de novo envia o mesmo ID de requisição, portanto não pode iniciar um segundo reset.", + "anthropicGrant.retrySame": "Repetir a mesma requisição", + "anthropicGrant.checkResult": "Reler conta", + "anthropicGrant.checking": "Relendo…", + "api.title": "Acesso à API", + "api.subtitle": "Use chaves de API geradas para acessar o proxy opencodex a partir de apps externos. As chaves se autenticam pelo cabeçalho {authHeader}; veja na tabela abaixo o que cada endpoint aceita.", + "api.baseUrl": "URL base", + "api.responsesEndpoint": "API Responses", + "api.chatCompletionsEndpoint": "API Chat Completions", + "api.messagesEndpoint": "API Messages", + "api.modelsEndpoint": "API Models", + "api.surface.on": "Ativado", + "api.surface.off": "Desativado", + "api.surface.source.fixed": "Sempre disponível", + "api.surface.source.explicit": "Configuração explícita", + "api.surface.source.inherited": "Herdado das configurações do Claude", + "api.surface.source.invalid": "Valor inválido — fechado", + "api.surface.messagesToggle": "Disponibilizar a API Messages", + "api.surface.messagesCloseNote": "Desativar o Messages também desativa a integração com o Claude, para que uma versão mais antiga do opencodex mantenha o endpoint fechado também.", + "api.surface.openClaude": "Configurações do Claude", + "api.surface.toggleFailed": "Não foi possível salvar a configuração do Messages.", + "api.surface.toggleUnavailable": "Esta versão do proxy não permite alterar a API Messages aqui. Use a página do Claude.", + "api.surface.closedNote": "As requisições a este endpoint são recusadas com 403.", + "api.endpointNote": "Use a URL base com clientes compatíveis com OpenAI. Responses e Chat Completions são expostos em /v1.", + "api.endpointsTitle": "Endpoints", + "api.authTitle": "Autenticação", + "api.authLoopback": "O acesso por loopback depende da rota; clientes de áudio independentes exigem uma chave de dados do OpenCodex. Binds remotos exigem uma chave de dados ou OPENCODEX_API_AUTH_TOKEN.", + "api.authBaseUrlNote": "Configure os clientes com a URL base e depois escolha o endpoint específico do protocolo abaixo.", + "api.newKeyTitle": "Nova chave criada", + "api.newKeyNote": "Copie esta chave agora — ela não será exibida novamente.", + "api.copy": "Copiar", + "api.copied": "Copiado", + "api.dismiss": "Dispensar", + "api.generateTitle": "Gerar chave", + "api.keyNamePlaceholder": "Nome da chave (opcional)", + "api.generate": "Gerar", + "api.generating": "Criando…", + "api.activeKeys": "Chaves ativas ({count})", + "api.activeKeysLoading": "Chaves ativas", + "api.noKeys": "Nenhuma chave de API ainda. Gere uma acima.", + "api.workspace.sections": "Seções da API", + "api.section.keys": "Chaves", + "api.section.connect": "Conectar", + "api.section.endpoints": "Endpoints", + "api.section.models": "Modelos", + "api.section.examples": "Exemplos", + "api.section.plan": "Prévia do caminho", + "api.plan.title": "Prévia do caminho da requisição", + "api.plan.description": "Mostra o caminho que uma requisição para este modelo percorreria pelo proxy. A prévia não envia nada ao upstream e não tem custo.", + "api.plan.modeNote": "Nativo descreve como a requisição é entregue. Não é um resultado de verificação.", + "api.plan.unavailable": "Esta versão do proxy não oferece prévias de caminho.", + "api.plan.noModels": "Ainda não há modelos para pré-visualizar.", + "api.plan.model": "Modelo", + "api.plan.inbound": "API do cliente", + "api.plan.features": "Recursos da requisição", + "api.plan.preview": "Pré-visualizar", + "api.plan.previewing": "Pré-visualizando…", + "api.plan.failed": "Não foi possível calcular a prévia.", + "api.plan.overallMode": "Entrega", + "api.plan.routeKind": "Rota", + "api.plan.reasons": "Motivos", + "api.plan.guaranteed": "Garantido por todos os candidatos", + "api.plan.partial": "Apenas alguns candidatos", + "api.plan.policyRevision": "Revisão da política", + "api.plan.noCandidates": "Nenhuma rota corresponde a este modelo.", + "api.plan.ineligible": "Recusado pela política atual", + "api.plan.requestPath": "Caminho da requisição", + "api.plan.responsePath": "Caminho da resposta", + "api.plan.fidelity": "Fidelidade", + "api.plan.none": "Nenhum", + "api.plan.noPath": "Sem caminho", + "api.plan.noFeatures": "Nenhum recurso de requisição selecionado.", + "api.plan.mode.native": "Nativo", + "api.plan.mode.translated": "Traduzido", + "api.plan.mode.legacyBridge": "Ponte legada", + "api.plan.mode.blocked": "Bloqueado", + "api.plan.fidelity.preserved": "Preservada", + "api.plan.fidelity.degraded": "Degradada", + "api.plan.fidelity.unknown": "Desconhecida", + "api.plan.routeKind.direct": "Direta", + "api.plan.routeKind.combo": "Combo", + "api.plan.routeKind.policy": "Política de roteamento", + "api.plan.routeKind.unknown": "Modelo desconhecido", + "api.plan.disposition.passthrough": "Repassado", + "api.plan.disposition.translated": "Traduzido", + "api.plan.disposition.degraded": "Degradado", + "api.plan.disposition.unsupported": "Descartado", + "api.plan.disposition.unknown": "Desconhecido", + "api.workspace.details": "Detalhes da chave de API", + "api.workspace.keyDetails": "Detalhes da chave", + "api.workspace.keyPrefix": "Prefixo da chave", + "api.workspace.deleteKey": "Excluir chave", + "api.workspace.deleteConfirm": "Tem certeza de que deseja excluir esta chave? Isso não pode ser desfeito.", + "api.workspace.usageExamples": "Exemplos de uso", + "api.copyUrlHint": "Clique para copiar a URL", + "api.urlCopied": "URL copiada", + "api.copyExampleHint": "Clique para copiar o exemplo", + "api.exampleCopied": "Exemplo copiado", + "api.colName": "Nome", + "api.colKey": "Chave", + "api.colCreated": "Criada em", + "api.confirm": "Confirmar", + "api.deleteAria": "Excluir chave de API", + "api.modelsTitle": "Catálogo de modelos externos", + "api.modelsCount": "{count} chamável(is)", + "api.modelsLoading": "Carregando modelos…", + "api.modelsSearch": "Buscar modelos", + "api.modelsSubtitle": "Use exatamente estes IDs de modelo com /v1/models e o protocolo de entrada escolhido.", + "api.modelsEmpty": "Ainda não há modelos chamáveis externamente.", + "api.modelsNoMatch": "Nenhum modelo corresponde a “{query}”.", + "api.modelsLoadFailed": "Não foi possível carregar o catálogo de modelos externos.", + "api.colModel": "Modelo", + "api.colSource": "Origem", + "api.colProtocols": "Protocolos", + "api.sourceNative": "Pool do ChatGPT", + "api.sourceCombo": "Rota combo", + "api.sourceCustom": "Personalizado", + "api.protocolResponses": "Responses", + "api.protocolChatCompletions": "Chat Completions", + "api.protocolMessages": "Messages", + "api.copyModelId": "Copiar ID", + "api.modelCopied": "Copiado", + "api.testModel": "Testar", + "api.testingModel": "Testando…", + "api.testSucceeded": "OK", + "api.testFailed": "Falhou", + "api.usageChatTitle": "Exemplo de Chat Completions", + "api.usageResponsesTitle": "Exemplo de Responses", + "api.usageMessagesTitle": "Exemplo de Messages", + "api.usageSampleInput": "Olá, mundo!", + "api.clientConfig.title": "Configuração do cliente", + "api.clientConfig.rowsLabel": "Conectar um cliente", + "api.clientConfig.details": "Detalhes", + "api.clientConfig.detailsAria": "Detalhes da configuração de {client}", + "api.clientConfig.copyAria": "Copiar a configuração de {client}", + "api.clientConfig.downloadAria": "Baixar a configuração de {client}", + "api.clientConfig.rowMeta": "{destination} · {count} modelo(s)", + "api.clientConfig.rowError": "Não foi possível gerar a configuração de {client}.", + "api.clientConfig.copiedAnnounceClient": "Configuração de {client} copiada para a área de transferência.", + "api.clientConfig.clientOpencode": "OpenCode", + "api.clientConfig.clientPi": "Pi", + "api.clientConfig.clientOmp": "OMP", + "api.clientConfig.clientHermes": "Hermes", + "api.clientConfig.clientOpenclaw": "OpenClaw", + "api.clientConfig.clientKimi": "Kimi Code", + "api.clientConfig.clientGajae": "gjc", + "api.clientConfig.clientDsh": "DeepSeek Harness (DSH)", + "api.clientConfig.clientMcode": "MiniMax Code", + "api.clientConfig.clientZcode": "ZCode", + "api.clientConfig.clientPrime": "Prime Agent", + "api.clientConfig.clientAside": "Aside", + "api.clientConfig.clientRaycast": "Raycast", + "api.clientConfig.clientOmo": "omo", + "api.clientConfig.clientCline": "Cline CLI", + "api.clientConfig.clientKilo": "Kilo", + "api.clientConfig.clientDroid": "Factory Droid", + "api.clientConfig.clineBundle": "Este é um pacote de dois arquivos, não um arquivo de configurações do Cline. Mescle settings em providers.json e catalog no models.json vizinho. Use o interruptor de integração para uma gravação com backup.", + "api.clientConfig.clineDownloaded": "{filename} baixado. Nada foi alterado ainda; settings e catalog pertencem a dois arquivos separados do Cline.", + "api.clientConfig.copy": "Copiar configuração", + "api.clientConfig.download": "Baixar", + "api.clientConfig.loading": "Gerando configuração do cliente…", + "api.clientConfig.jsonLabel": "Configuração de {client}", + "api.clientConfig.destination": "Arquivo de destino", + "api.clientConfig.envHint": "Defina a chave antes de iniciar", + "api.clientConfig.mergeWarning": "Mescle isto no arquivo de destino. Substituí-lo apagaria seus outros provedores e configurações de MCP.", + "api.clientConfig.modelCount": "{count} modelo(s) exportado(s)", + "api.clientConfig.missingLimits": "{count} de {total} modelo(s) saem sem limite de contexto; o cliente aplica seus próprios padrões.", + "api.clientConfig.noKeyYet": "{env} ainda não tem nenhuma chave por trás. Gere uma chave acima antes de usar esta configuração fora do loopback.", + "api.clientConfig.loadFailed": "Não foi possível ler a lista de modelos, então nenhuma configuração de cliente foi gerada.", + "api.clientConfig.copiedAnnounce": "Configuração do cliente copiada para a área de transferência.", + "api.clientConfig.copyFailed": "Não foi possível copiar a configuração do cliente.", + "api.clientConfig.downloadedAnnounce": "{filename} baixado. Nada foi alterado ainda — mescle-o você mesmo em {destination}.", + "api.clientConfig.whereDisclosure": "Onde este arquivo vai", + "api.clientConfig.whereBody": "O destino acima é o caminho global. Um arquivo de configuração local do projeto, no diretório de trabalho, tem precedência sobre ele, e o cliente lê a chave da variável de ambiente indicada na configuração — nunca deste arquivo.", + "api.keysLoadFailed": "Não foi possível carregar as chaves de API.", + "api.createFailed": "Não foi possível criar a chave de API.", + "api.deleteFailed": "Não foi possível excluir a chave de API.", + "api.auth.endpoint": "Endpoint", + "api.auth.required": "Obrigatória", + "api.auth.accepted": "Aceita", + "api.auth.rejected": "Não aceita", + "api.auth.testProtocol": "Testar {protocol}", + "api.auth.testNeedsFreshKey": "Gere uma chave e mantenha seu valor único na tela para executar um teste autenticado.", + "api.key.name": "Nome da chave", + "api.key.rename": "Renomear", + "api.key.saveName": "Salvar nome", + "api.key.renaming": "Salvando…", + "api.key.renameFailed": "Não foi possível renomear a chave. Seu rascunho foi mantido.", + "api.key.deleting": "Excluindo…", + "api.rotation.title": "Rotação de chave", + "api.rotation.description": "Emita uma chave substituta enquanto a chave atual continua válida por um curto período de sobreposição.", + "api.rotation.start": "Iniciar rotação", + "api.rotation.starting": "Iniciando…", + "api.rotation.pending": "A rotação está pendente. Atualize e verifique o cliente antes de confirmar.", + "api.rotation.expires": "A sobreposição expira em:", + "api.rotation.secretOnce": "Chave substituta — exibida uma única vez. Copie-a antes de fechar este aviso.", + "api.rotation.commit": "Confirmar rotação", + "api.rotation.abort": "Cancelar rotação", + "api.rotation.failed": "A ação de rotação não foi concluída. Atualize a página antes de tentar novamente.", + "api.rotation.startFailed": "Não foi possível iniciar a rotação da chave.", + "api.key.copyFailed": "Não foi possível copiar a chave. Selecione-a e copie manualmente antes de fechar este painel.", + "api.attribution.title": "Uso atribuído", + "api.attribution.requests7d": "Requisições, últimos 7 dias", + "api.attribution.totalRequests": "Total de requisições atribuídas", + "api.attribution.totalRequestsAvailable": "Requisições no histórico disponível", + "api.attribution.sinceAvailable": "Atribuição disponível desde", + "api.attribution.lastUsed": "Último uso", + "api.attribution.since": "Atribuição disponível desde", + "api.attribution.neverUsed": "Não usada desde o início da atribuição", + "api.attribution.unavailable": "Uso indisponível", + "api.attribution.unavailableDetail": "Nenhum uso foi atribuído ainda. As requisições registradas antes do início da atribuição não podem ser atribuídas retroativamente.", + "api.attribution.ambiguous": "Duas chaves compartilham este ID, então o uso não pode ser atribuído a nenhuma delas. Dê a cada chave um ID exclusivo no arquivo de configuração.", + "api.attribution.railAmbiguous": "ID duplicado", + "claude.subtitle": "Use GPT, Gemini e outros modelos dentro do Claude Code.", + "claude.pageTitle": "Claude Code", + "claude.workspace.settings": "Geral", + "claude.enabledLabel": "Conexão com o Claude", + "claude.enabledHint": "Quando desativada, o Claude Code não consegue usar este proxy.", + "claude.authMode": "Modo de autenticação", + "claude.authModeHint": "Assinatura exige conta Claude; Proxy funciona sem conta Anthropic", + "claude.authModeSubscription": "Assinatura (conta Claude)", + "claude.authModeProxy": "Proxy (sem necessidade de conta)", + "claude.authModeAuto": "Automático (detectar autenticação do Claude)", + "claude.effectiveMode.label": "Vale a partir da próxima inicialização", + "claude.effectiveMode.manual": "Manual: {mode}", + "claude.effectiveMode.autoPresent": "Automático: assinatura — autenticação do Claude encontrada via {source}", + "claude.effectiveMode.autoAbsent": "Automático: modo proxy — nenhuma autenticação do Claude encontrada", + "claude.effectiveMode.autoUnknown": "Automático: assinatura — não foi possível verificar a autenticação", + "claude.effectiveMode.admissionKey": "A chave de API deste proxy continua sendo enviada.", + "claude.authSource.claude-json-oauth": "conta Claude", + "claude.authSource.claude-credentials-file": "arquivo de credenciais", + "claude.authSource.macos-keychain": "Keychain do macOS", + "claude.authSource.exported-env": "variável de ambiente", + "claude.authSource.unknown": "uma credencial detectada", + "claude.systemEnv": "Conexão automática", + "claude.systemEnvDesc": "Quando ativada, executar claude em qualquer terminal passa automaticamente pelo proxy.", + "claude.systemEnvUnsupported": "A conexão automática está disponível apenas no macOS. Neste sistema, inicie o Claude com {cmd}.", + "claude.systemEnvWarn": "⚠ É preciso fechar totalmente e reabrir o aplicativo de terminal para que isto tenha efeito. Não recomendado.", + "claude.fastMode": "Modo rápido (OpenAI)", + "claude.fastModeDesc": "Controla o service_tier dos modelos OpenAI. ON = priority (mais rápido). OFF = default. Auto = repasse (o cliente decide).", + "claude.fastAuto": "Auto", + "claude.fastOn": "ON", + "claude.fastOff": "OFF", + "claude.autoContext": "Usar contexto grande automaticamente", + "claude.autoContextDesc": "Controla até onde vai a marcação de 1M. ON: qualquer modelo cuja janela comporte o limite de compactação ganha uma linha de contexto grande. OFF: só os modelos de 1M de verdade ganham uma.", + "claude.autoContextInert": "Inativo porque existe um valor legado de tamanho de contexto (maxContextTokens) no arquivo de configuração. Remova-o de lá para reativar.", + "claude.autoCompactWindow": "Ponto de resumo automático", + "claude.autoCompactDefault": "{value} (padrão)", + "claude.autoCompactWindowDesc": "As mensagens mais antigas são resumidas quando a conversa chega a este ponto. Ele nunca excede o limite de cada modelo, então modelos de 200k não são afetados.", + "claude.autoCompactWindowWarn": "Alterar isto pode quebrar modelos GPT — se for definido acima do limite real do modelo, as conversas darão erro antes de o resumo entrar em ação.", + "claude.injectAgents": "Registrar subagentes automaticamente", + "claude.injectAgentsDesc": "Registra os modelos escolhidos na aba Subagentes (mais o modelo padrão atual) como agentes despacháveis do Claude Code (ocx-*). Vale a partir da próxima sessão.", + "claude.webSearchSidecar": "Substituição do sidecar de busca na web", + "claude.webSearchSidecarHint": "Substitui o sidecar principal de busca na web nas requisições do Claude Code.", + "claude.visionSidecar": "Substituição do sidecar de visão", + "claude.visionSidecarHint": "Substitui o sidecar principal de visão nas requisições do Claude Code.", + "claude.useMainSetting": "Usar configuração principal", + "claude.sidecarModelPlaceholder": "Modelo da configuração principal", + "claude.quickstart": "Primeiros passos", + "claude.quickstartHint": "{cmd} abre o Claude Code por meio do proxy. Seu login no claude.ai continua ativo.", + "claude.manualEnv": "Configuração manual (avançado)", + "claude.smallFastModel": "Modelo auxiliar em segundo plano", + "claude.smallFastModelHint": "O modelo que o Claude Code usa em tarefas de segundo plano, como resumos de conversa e detecção de tópico. O alias de subagente haiku também o utiliza. Vazio = padrão do Claude (Haiku).", + "claude.smallFastModelAccurateHint": "O modelo que o Claude Code usa em tarefas de segundo plano, como resumos de conversa e detecção de tópico. O alias de subagente haiku também o utiliza.", + "claude.smallFastModelUnsetOption": "Deixar o Claude Code escolher (modelo nativo)", + "claude.smallFastModelNativeWarning": "Quando não definido, o OpenCodex deixa sem definir as substituições do modelo auxiliar. O Claude Code pode usar seu modelo Sonnet nativo, o que pode gerar cobranças do seu provedor nativo.", + "claude.slotUnset": "Usar padrão do Claude", + "claude.modelMap": "Interceptação de modelo", + "claude.modelMapHint": "Intercepta requisições de um modelo específico e as redireciona para o modelo que você escolher. Vazio por padrão: nada acontece até você adicionar uma regra.", + "claude.mapFrom": "Modelo original (ex.: claude-sonnet-4-5)", + "claude.mapTo": "Trocar por (ex.: gemini/gemini-3-pro)", + "claude.addMapping": "Adicionar regra", + "claude.removeMapping": "Remover regra", + "claude.aliases": "Modelos disponíveis", + "claude.aliasesHint": "Modelos que aparecem no menu /model do Claude Code.", + "claude.aliasProviderOther": "Outros", + "claude.loading": "Carregando…", + "claude.loadFail": "Falha ao carregar as configurações do Claude", + "claude.saved": "Salvo.", + "claude.saveFailed": "Falha ao salvar", + "claude.networkError": "Erro de rede — o proxy está em execução?", + "claude.toggleAria": "Alternar conexão com o Claude", + "claude.none": "Nenhum", + "cws.loading": "Carregando combos…", + "cws.loadFailed": "Não foi possível carregar os combos.", + "cws.saveFailed": "Não foi possível salvar o combo.", + "cws.removeFailed": "Não foi possível remover o combo.", + "cws.saved": "Combo salvo.", + "cws.created": "{model} criado.", + "cws.removed": "combo/{id} removido.", + "cws.renamed": "{from} renomeado para {to}.", + "cws.add": "Adicionar combo", + "cws.addTitle": "Adicionar combo", + "cws.addSubtitle": "Crie um modelo virtual entre provedores e escolha o nome exato do modelo que os clientes vão solicitar.", + "cws.create": "Criar combo", + "cws.jev.create": "Criar JEV Auto", + "cws.jev.exists": "O JEV Auto já existe.", + "cws.jev.setupHint": "Crie um Combo opcional e totalmente editável. O JEV escolhe um destino permitido e um esforço de raciocínio para cada requisição.", + "cws.jev.decisionService": "Serviço de decisão", + "cws.jev.decisionServiceDefault": "TypeSafe JEV (padrão)", + "cws.err.invalidDecisionProvider": "O serviço de decisão {name} não pode ser usado ({reason}). Escolha outro serviço ou o TypeSafe JEV.", + "cws.jev.decisionIssue.missing": "não configurado", + "cws.jev.decisionIssue.notDecision": "não é um serviço de decisão", + "cws.jev.decisionIssue.disabled": "desativado", + "cws.jev.decisionIssue.endpoint": "a URL base deve terminar em /systemone", + "cws.jev.decisionIssue.model": "sem modelo padrão", + "cws.jev.decisionServiceDefaultHint": "O serviço de decisão TypeSafe hospedado, autenticado com a chave do provedor jev.", + "cws.jev.decisionServiceHint": "Serviço de decisão auto-hospedado. Apenas a chave de API do próprio provedor é enviada ao seu endpoint:", + "cws.jev.decisionServicesTitle": "Serviços de decisão do JEV", + "cws.jev.decisionTimeout": "Timeout da decisão (ms)", + "cws.jev.decisionTimeoutHint": "Deixe vazio para o padrão ({default} ms); permitido de {min} a {max} ms. Quando expira, o JEV recorre ao primeiro destino elegível. Aumente para modelos auto-hospedados lentos ou frios.", + "cws.jev.decisionTimeoutShort": "Timeout de {ms} ms", + "cws.jev.decisionTimeoutDefaultShort": "Timeout padrão", + "cws.jev.decisionMethod": "Método de decisão", + "cws.jev.method.typesafe": "TypeSafe JEV (padrão)", + "cws.jev.method.systemone": "Servidor compatível com System One", + "cws.jev.method.model": "modelo do opencodex", + "cws.jev.method.systemoneEmpty": "Nenhum servidor System One está configurado. Adicione um provedor com o adaptador jev-decision cuja URL base termine em /systemone, por exemplo um servidor Ollama executando o tev1.", + "cws.jev.decisionModel": "Modelo de decisão", + "cws.jev.decisionModelHint": "Qualquer modelo que o opencodex roteie. Ele recebe o estado de decisão limitado e as opções permitidas e deve responder com uma chave de opção em JSON. Usa as credenciais armazenadas em seu próprio provedor, nunca as do chamador, e não pode ser este combo nem outro combo JEV.", + "cws.err.invalidDecisionModel": "Escolha um modelo de decisão que o opencodex consiga rotear. Ele não pode ser este combo nem outro combo JEV.", + "cws.jev.test": "Testar", + "cws.jev.testing": "Testando…", + "cws.jev.testHint": "Envia uma tarefa sintética com opções de exemplo ao método selecionado. Nada é salvo.", + "cws.jev.testOk": "Respondeu em {ms} ms via {backend}.", + "cws.jev.testFailOpen": "Falhou em modo aberto ({gate}) após {ms} ms. As requisições usariam o primeiro destino elegível.", + "cws.jev.testError": "Não foi possível executar o teste: {error}", + "cws.jev.backend.typesafe": "TypeSafe", + "cws.jev.backend.systemone": "Servidor System One", + "cws.jev.backend.model": "modelo do opencodex", + "cws.jev.backend.unknown": "Desconhecido (registros antigos)", + "cws.jev.stats.backends": "Decisões por método", + "cws.jev.stats.backend": "Método", + "cws.jev.stats.applied": "Aplicadas", + "cws.jev.stats.latency": "Latência média", + "cws.err.invalidDecisionTimeout": "O timeout da decisão deve ser um inteiro de {min} a {max} ms, ou vazio para o padrão.", + "cws.jev.failOpen": "Destino de fail-open", + "cws.jev.modelProfile": "Notas adicionais de modelo para o JEV", + "cws.jev.modelProfilePlaceholder": "Opcional: deixe em branco para nenhuma nota adicional.", + "cws.jev.modelProfileHint": "Notas opcionais que complementam (nunca substituem) o perfil padrão integrado: capacidade, contexto e custo relativo da assinatura. Enviadas ao serviço de decisão selecionado a cada decisão do JEV.", + "cws.err.invalidModelProfile": "As notas de modelo devem ter no máximo 512 caracteres; quebras de linha e tabulações são os únicos caracteres de controle permitidos.", + "cws.jev.allowedEfforts": "O JEV pode selecionar", + "cws.jev.efforts": "Esforços de raciocínio: {efforts}", + "cws.jev.effortsUnknown": "Esforços de raciocínio não informados", + "cws.jev.effortsNone": "Sem esforço de raciocínio explícito", + "cws.jev.stats.tab": "Estatísticas", + "cws.jev.stats.range": "Período das estatísticas do JEV", + "cws.jev.stats.refresh": "Atualizar", + "cws.jev.stats.loading": "Carregando estatísticas do JEV", + "cws.jev.stats.loadFailed": "Não foi possível carregar as estatísticas do JEV.", + "cws.jev.stats.historyIncomplete": "Alguns registros antigos do JEV foram omitidos, então estes totais podem estar incompletos.", + "cws.jev.stats.emptyTitle": "Ainda não há decisões do JEV", + "cws.jev.stats.emptyBody": "Execute este combo para registrar as escolhas e o uso de tokens dos modelos. Decisões tomadas antes da instalação do suporte a estatísticas não estão disponíveis.", + "cws.jev.stats.decisions": "Decisões", + "cws.jev.stats.appliedAndFailOpen": "{applied} aplicadas · {failOpen} em fail-open", + "cws.jev.stats.modelTokens": "Tokens do modelo", + "cws.jev.stats.decisionTokens": "Tokens de decisão do JEV", + "cws.jev.stats.measuredAttempts": "{measured}/{total} tentativas medidas", + "cws.jev.stats.measuredDecisions": "{measured}/{total} decisões informaram uso", + "cws.jev.stats.successful": "{count} requisições bem-sucedidas", + "cws.jev.stats.fallbackOne": "{count} fallback de modelo", + "cws.jev.stats.fallbackMany": "{count} fallbacks de modelo", + "cws.jev.stats.averageLatency": "Latência média do JEV: {value}", + "cws.jev.stats.averageConfidence": "Confiança média: {value}", + "cws.jev.stats.gates": "Portões de decisão do JEV", + "cws.jev.stats.model": "Modelo", + "cws.jev.stats.otherModels": "Outros modelos", + "cws.jev.stats.picks": "Escolhas", + "cws.jev.stats.efforts": "Esforços", + "cws.jev.stats.attempts": "Tentativas", + "cws.jev.stats.input": "Entrada", + "cws.jev.stats.output": "Saída", + "cws.jev.stats.reasoning": "Raciocínio", + "cws.jev.stats.cacheReadWrite": "Cache L/E", + "cws.jev.stats.total": "Total", + "cws.jev.stats.noEffort": "nenhum", + "cws.jev.stats.measuredShort": "medido", + "cws.jev.stats.tokenFootnote": "Os tokens do modelo vêm das tentativas físicas nos destinos, incluindo novas tentativas e fallbacks. Os tokens de decisão do JEV são informados separadamente pelo serviço de decisão.", + "cws.railAria": "Lista de combos", + "cws.searchPlaceholder": "Buscar combos ou destinos…", + "cws.noSearchResults": "Nenhum combo corresponde à busca.", + "cws.group.failover": "Failover", + "cws.group.roundRobin": "Round-robin", + "cws.group.other": "Outras estratégias", + "cws.targetCount": "{count} destinos", + "cws.targetCountOne": "1 destino", + "cws.overviewTitle": "Combos", + "cws.overviewBlurb": "Modelos virtuais que roteiam entre destinos provedor/modelo com failover, round-robin, sorteio ponderado, menos usado ou reinício de cota mais próximo.", + "cws.count.total": "Total", + "cws.count.failover": "Failover", + "cws.count.roundRobin": "Round-robin", + "cws.count.other": "Outros", + "cws.howTitle": "Como funciona", + "cws.howBody": "Peça ao Codex o nome público do modelo do combo. Se não houver um, o padrão é combo/. O OpenCodex seleciona um destino e só passa ao próximo em falhas recuperáveis do upstream. Se nenhum destino estiver disponível, a requisição falha em modo fechado em vez de usar o provedor padrão global.", + "cws.attentionTitle": "Requer atenção", + "cws.attention.empty": "Nenhum destino configurado", + "cws.attention.few": "Apenas um destino — o failover não tem para onde ir", + "cws.attention.catalogOmitted": "Ausente do catálogo de modelos — as capacidades dos membros estão incompletas ou incompatíveis (janela de contexto/metadados ausentes, ou interseção de modalidades vazia). O roteamento por alias continua funcionando", + "cws.attention.allTargetsExhausted": "Todos os destinos habilitados estão sem cota", + "cws.emptyTitle": "Crie seu primeiro combo", + "cws.empty.createDesc": "Dê nome a um modelo virtual e encadeie dois ou mais backends.", + "cws.backToAll": "Voltar para todos os combos", + "cws.allCombos": "Todos os combos", + "cws.copyModel": "Copiar id", + "cws.copied": "Copiado", + "cws.tabsLabel": "Seções de detalhe do combo", + "cws.tab.config": "Configuração", + "cws.tab.about": "Sobre", + "cws.strategy": "Estratégia", + "cws.strategy.failover": "Failover", + "cws.strategy.roundRobin": "Round-robin", + "cws.strategy.random": "Aleatória", + "cws.strategy.leastUsed": "Menos usado", + "cws.strategy.resetWindow": "Janela de reinício", + "cws.strategy.jev": "JEV", + "cws.strategy.failoverHint": "Tenta os destinos em ordem. Se o primeiro falhar com um erro recuperável (limite de taxa, indisponibilidade, bloqueio de assinatura), passa ao próximo.", + "cws.strategy.roundRobinHint": "Distribui o tráfego de forma determinística por peso. Mantém cada destino selecionado por um lote de requisições bem-sucedidas e depois avança.", + "cws.strategy.randomHint": "Sorteia um destino elegível por requisição, com probabilidade proporcional ao peso. Sem afinidade entre requisições.", + "cws.strategy.leastUsedHint": "Encaminha cada requisição ao destino elegível com menos sucessos registrados. As contagens reiniciam com o proxy.", + "cws.strategy.resetWindowHint": "Prefere o destino elegível cuja janela de cota reinicia primeiro. Recorre à ordem da configuração quando faltam dados de cota.", + "cws.strategy.jevHint": "Pede ao serviço de decisão JEV selecionado que escolha um destino elegível e um esforço compatível. Se a decisão estiver indisponível, usa o primeiro destino elegível.", + "cws.field.id": "Id do combo", + "cws.field.idHint": "Os clientes solicitarão {model}", + "cws.field.idInternalHint": "Id interno do combo. Você pode alterá-lo após a criação.", + "cws.field.idHintEdit": "Renomear move o combo para um novo id. Os clientes solicitam {model}.", + "cws.field.alias": "Nome público do modelo", + "cws.field.aliasPlaceholder": "deepseek-v4-flash ou fornecedor/modelo", + "cws.field.aliasHint": "Opcional. Use um nome simples sem prefixo, um prefixo personalizado como fornecedor/modelo, ou deixe em branco para usar combo/.", + "cws.field.nativeAlias": "Alias nativo da OpenAI", + "cws.field.nativeAliasHint": "Permite que este combo assuma um id de modelo nativo da OpenAI sem qualificador e compatível. As rotas da OpenAI qualificadas por conta e por provedor permanecem separadas.", + "cws.field.displayName": "Nome de exibição", + "cws.field.displayNameHint": "Rótulo deste combo no seletor. Obrigatório quando o alias nativo da OpenAI está habilitado.", + "cws.field.stickyLimit": "Sucessos fixos antes de alternar", + "cws.field.stickyLimitHint": "Mantém o destino selecionado por este número de requisições bem-sucedidas antes de o seletor ponderado avançar.", + "cws.field.defaultEffort": "Raciocínio padrão", + "cws.field.defaultEffortNone": "Nenhum (padrão do destino)", + "cws.field.defaultEffortHint": "Usado somente quando o cliente omite o esforço de raciocínio. As opções são a interseção dos esforços informados pelos destinos selecionados; destinos sem metadados de esforço no catálogo não oferecem nenhuma.", + "cws.capability.imageInputUnavailable": "Indisponível até que todos os destinos selecionados aceitem entrada de imagem.", + "cws.capability.imageInputHint": "Ativado por padrão quando todos os destinos aceitam imagens. Desative para aceitar somente texto.", + "cws.capability.imageInput": "Imagem / multimodal", + "cws.capability.adaptiveEffort": "Escala de raciocínio adaptativa", + "cws.capability.adaptiveEffortHint": "Desativado: um destino sem controle de raciocínio oculta o seletor de esforço para o combo inteiro. Ativado: esses destinos continuam utilizáveis e o seletor mantém os níveis compartilhados pelos demais destinos.", + "cws.capabilities": "Capacidades", + "cws.field.defaultEffortUnsupported": "Este esforço não está na escala comum dos destinos — será ignorado ou ajustado no momento da requisição.", + "cws.field.defaultEffortUnsupportedOption": "fora da interseção", + "cws.targets": "Destinos", + "cws.targets.failoverHint": "A ordem importa — o primeiro é o principal.", + "cws.targets.roundRobinHint": "Os pesos controlam a seleção relativa determinística; a ordem desempata no anel de rotação.", + "cws.targets.randomHint": "Os pesos controlam a probabilidade de cada sorteio; a ordem não importa.", + "cws.targets.leastUsedHint": "A ordem só desempata entre destinos igualmente usados.", + "cws.targets.resetWindowHint": "A ordem vale quando faltam dados de cota ou há empate.", + "cws.targets.jevHint": "Somente estes destinos podem ser selecionados. A ordem define o destino de fail-open e o fallback posterior do Combo.", + "cws.target.provider": "Provedor", + "cws.target.model": "Modelo", + "cws.target.weight": "Peso", + "cws.target.pickProvider": "Selecione o provedor…", + "cws.target.pickProviderFirst": "Selecione um provedor primeiro…", + "cws.target.pickModel": "Selecione o modelo…", + "cws.target.noModels": "Nenhum modelo para este provedor", + "cws.target.modelPlaceholder": "id do modelo", + "cws.target.add": "Adicionar destino", + "cws.target.drag": "Arraste para reordenar", + "cws.target.moveUp": "Mover para cima", + "cws.target.moveDown": "Mover para baixo", + "cws.quota.available": "Disponível", + "cws.quota.exhausted": "Sem cota", + "cws.quota.unknown": "Cota desconhecida", + "cws.quota.allExhausted": "Todos os destinos habilitados estão sem cota. Escolha outro destino ou aguarde a recuperação da cota.", + "cws.aboutTitle": "Execução", + "cws.aboutBody": "Destinos que falham entram em pausa breve, respeitando o Retry-After. Erros de requisição inválida ou de contexto não passam ao próximo destino. Cada destino adapta o esforço às próprias capacidades; combos esgotados falham em modo fechado. Logs e Uso mantêm as tentativas físicas em ordem e o uso por tentativa.", + "cws.removeConfirmTitle": "Remover {model}?", + "cws.removeConfirmDesc": "Isso remove o modelo virtual da configuração e do catálogo do Codex. Nenhum provedor é excluído.", + "cws.unsavedTitle": "Alterações não salvas", + "cws.unsavedDesc": "Descartar as edições deste combo e continuar?", + "cws.keepEditing": "Continuar editando", + "cws.err.missingId": "O id do combo é obrigatório.", + "cws.err.invalidId": "O id deve começar com letra ou número e usar apenas letras, números, pontos, sublinhados ou hifens (máx. 64).", + "cws.err.duplicateId": "Já existe um combo com este id.", + "cws.err.invalidAlias": "O alias deve usar letras, números, pontos, sublinhados ou hifens, com no máximo um segmento \"/\".", + "cws.err.aliasReservedNamespace": "O alias não pode usar o namespace reservado \"combo/\".", + "cws.err.aliasNativeFamily": "Aliases simples da família nativa da OpenAI (gpt-*, o1-*, o3-*, o4-*, codex-*) não são permitidos.", + "cws.err.unsupportedNativeAlias": "O alias nativo deve ser um id de modelo simples da OpenAI atualmente compatível.", + "cws.err.missingNativeAliasDisplayName": "É necessário um nome de exibição para aliases nativos.", + "cws.err.invalidDisplayName": "O nome de exibição deve ter no máximo 128 caracteres e não conter caracteres de controle.", + "cws.err.duplicateAlias": "Outro combo já usa este alias.", + "cws.err.noTargets": "Adicione pelo menos um destino.", + "cws.err.incompleteTarget": "Cada destino precisa de um provedor e um modelo.", + "cws.target.disabled": "{name} (desabilitado)", + "cws.err.reservedNamespace": "Um provedor físico chamado combo precisa ser renomeado antes de criar combos.", + "cws.err.providerCollision": "O id do combo conflita com o nome de um provedor configurado.", + "cws.err.unknownProvider": "Cada destino deve usar um provedor configurado.", + "cws.err.duplicateTarget": "O mesmo destino provedor/modelo só pode aparecer uma vez.", + "cws.err.invalidStickyLimit": "Os sucessos fixos devem ser um número inteiro de 1 a 100.", + "cws.err.invalidWeight": "Cada peso de round-robin deve ser um número inteiro de 1 a 10000.", + "cws.err.invalidReasoningEfforts": "Cada destino do JEV deve permitir pelo menos um esforço de raciocínio único e compatível.", + "cws.err.noEnabledTarget": "Pelo menos um destino deve usar um provedor habilitado.", + "claude.tabsLabel": "Cliente Claude", + "claude.tabCode": "Code", + "claude.tabDesktop": "Desktop", + "claudeDesktop.title": "Claude Desktop", + "claudeDesktop.subtitle": "Roteia cada família de modelos do Claude por um modelo disponível na porta {port} do proxy.", + "claudeDesktop.importJson": "Importar JSON", + "claudeDesktop.exportJson": "Exportar JSON", + "claudeDesktop.loading": "Carregando perfil do Claude Desktop…", + "claudeDesktop.loadFail": "Falha ao carregar o perfil do Claude Desktop.", + "claudeDesktop.retry": "Tentar novamente", + "claudeDesktop.saveFailed": "Falha ao salvar o perfil do Claude Desktop.", + "claudeDesktop.applyFailed": "O perfil foi salvo, mas não pôde ser aplicado.", + "claudeDesktop.updateFailed": "Falha na atualização do Claude Desktop.", + "claudeDesktop.savedApplied": "Perfil salvo e aplicado ao Claude Desktop.", + "claudeDesktop.appliedMarkerUnsaved": "Aplicado ao Claude Desktop, mas o marcador de aplicação não foi salvo — o estado salvo versus aplicado abaixo pode aparecer desatualizado até você aplicar novamente.", + "claudeDesktop.savedAppliedAnnounce": "Perfil do Claude Desktop salvo e aplicado.", + "claudeDesktop.saved": "Perfil salvo.", + "claudeDesktop.savedAnnounce": "Perfil do Claude Desktop salvo.", + "claudeDesktop.exported": "Perfil exportado como JSON.", + "claudeDesktop.importExpected": "Era esperado um perfil do Claude Desktop versão 1.", + "claudeDesktop.importReady": "JSON importado. Revise o rascunho e depois salve e aplique.", + "claudeDesktop.importedAnnounce": "JSON do perfil importado. As alterações não salvas estão prontas para revisão.", + "claudeDesktop.importInvalid": "O arquivo selecionado não é um perfil válido.", + "claudeDesktop.importFailed": "Falha na importação. {error}", + "claudeDesktop.moved": "{route} movido para {family}.", + "claudeDesktop.unsaved": "Alterações não salvas", + "claudeDesktop.upToDate": "O perfil está atualizado", + "claudeDesktop.saving": "Salvando…", + "claudeDesktop.applying": "Aplicando…", + "claudeDesktop.saveApply": "Salvar e aplicar", + "claudeDesktop.emptyTitle": "Nenhum modelo disponível", + "claudeDesktop.emptyHint": "Adicione ou habilite um provedor e volte para atribuir rotas ao Claude Desktop.", + "claudeDesktop.assignmentsLabel": "Atribuições das famílias de modelos do Claude", + "claudeDesktop.family.opus": "Opus", + "claudeDesktop.family.fable": "Fable", + "claudeDesktop.family.sonnet": "Sonnet", + "claudeDesktop.family.haiku": "Haiku", + "claudeDesktop.modelCountOne": "{count} modelo", + "claudeDesktop.modelCountMany": "{count} modelos", + "claudeDesktop.chooseDefault": "Escolha um padrão", + "claudeDesktop.temporaryDefault": "Padrão temporário", + "claudeDesktop.laneEmpty": "Solte um modelo aqui ou use o controle Mover.", + "claudeDesktop.laneNoMatch": "Nenhum modelo desta família corresponde à sua busca.", + "nav.grok": "Grok", + "grok.title": "Grok Build", + "grok.subtitle": "Modelos que o opencodex registrou na sua configuração do Grok.", + "grok.loading": "Carregando status do Grok…", + "grok.loadFail": "Não foi possível ler a configuração do Grok.", + "grok.notConfiguredTitle": "O Grok Build não está configurado", + "grok.notConfiguredHint": "Inicie ou reinicie o proxy com o Grok instalado e o opencodex grava um bloco gerenciado em:", + "grok.endpoint": "Endpoint", + "grok.colModel": "Modelo", + "grok.colAlias": "Alias do Grok", + "grok.colContext": "Contexto", + "grok.groupNative": "Modelos nativos", + "grok.groupRouted": "Modelos roteados", + "grok.enabledCount": "{on} de {total} registrados", + "grok.saved": "Seleção salva.", + "grok.savedApplied": "Seleção salva e gravada na sua configuração do Grok.", + "grok.saveFailed": "Não foi possível salvar a seleção do Grok.", + "grok.applyFailed": "Seleção salva, mas a configuração do Grok não pôde ser atualizada.", + "grok.applySkipped": "Seleção salva. A configuração do Grok não foi alterada.", + "grok.saveApply": "Salvar e aplicar", + "grok.saving": "Salvando…", + "grok.applying": "Aplicando…", + "grok.unsaved": "Alterações não salvas", + "grok.upToDate": "A seleção está atualizada", + "grok.toggleModel": "Registrar {id} no Grok", + "claudeDesktop.available": "Disponível", + "claudeDesktop.defaultBadge": "Padrão", + "claudeDesktop.supports1m": "1M", + "claudeDesktop.unavailable": "Indisponível", + "claudeDesktop.contextM": "contexto de {n}M", + "claudeDesktop.contextK": "contexto de {n}k", + "claudeDesktop.contextUnknown": "contexto desconhecido", + "claudeDesktop.alias": "Alias", + "claudeDesktop.useAsDefault": "Usar como padrão de {family}", + "claudeDesktop.moveTo": "Mover para", + "claudeDesktop.move": "Mover", + "claudeDesktop.status.applied": "Aplicado ao Desktop", + "claudeDesktop.status.stale": "Configuração desatualizada — aplique novamente", + "claudeDesktop.status.notApplied": "Não aplicado", + "claudeDesktop.status.notActiveProfile": "O Desktop está usando outro perfil — aplique novamente", + "claudeDesktop.status.disabled": "A integração com o Claude Desktop está desativada. Feche completamente e reabra o Desktop após ativá-la.", + "claudeDesktop.enableApply": "Ativar e aplicar", + "claudeDesktop.switchModeApply": "Trocar de modo e aplicar", + "claudeDesktop.status.appliedFirstParty": "Primeira parte: a aba Code do Desktop roteada pelo proxy local", + "claudeDesktop.mode.legend": "Modo de conexão", + "claudeDesktop.mode.firstParty": "Primeira parte", + "claudeDesktop.mode.gateway": "Gateway (terceiros)", + "claudeDesktop.mode.defaultBadge": "padrão", + "claudeDesktop.mode.current": "atual", + "claudeDesktop.mode.firstPartyHint": "O Desktop continua conectado ao claude.ai. A aba Code usa o OpenCodex quando a primeira parte do Desktop está ativada; a CLI independente tem seu próprio interruptor. Ambos leem o mesmo env das configurações.", + "claude.intercept.start": "Iniciar interceptação", + "claude.intercept.starting": "Iniciando interceptação…", + "claude.intercept.reason.disabled": "Ative primeiro o roteamento e a interceptação do Claude.", + "claude.intercept.reason.client_role": "Este processo é um cliente do hub. Ative a interceptação em um servidor local.", + "claude.intercept.reason.ephemeral_port": "Defina claudeCode.intercept.port quando a porta pública for efêmera.", + "claude.intercept.reason.port_in_use": "A porta {port} está em uso. Libere-a ou altere claudeCode.intercept.port.", + "claude.intercept.reason.port_mismatch": "Esta execução usa a porta {bound}; a porta configurada {configured} é diferente. Use a porta vinculada ou restaure o valor configurado.", + "claude.intercept.reason.failed": "Não foi possível iniciar a interceptação. Tente novamente após verificar a configuração do proxy local.", + "claude.intercept.reason.stopped": "O ciclo de vida da interceptação foi encerrado. Inicie um serviço OpenCodex em execução.", + "claude.firstParty.label": "Primeira parte da CLI do Claude Code", + "claude.firstParty.desc": "Permite que a CLI claude independente mantenha o login do Claude enquanto seu tráfego passa pelo proxy de interceptação local.", + "claude.firstParty.aria": "Alternar primeira parte da CLI do Claude Code", + "claude.firstParty.risk": "Risco para a conta: a primeira parte encaminha o tráfego da assinatura do Claude por um proxy local de interceptação. A Anthropic pode tratar isso como violação dos termos e suspender a conta.", + "claude.firstParty.shared": "Quando o env compartilhado do proxy é aplicado, o outro cliente do Claude também passa pelo proxy local como um relay inalterado; o TLS é encerrado localmente. Defina NO_PROXY='*' no shell para uso totalmente nativo no terminal.", + "claude.firstParty.deadProxy": "O env das configurações do Claude aponta para um proxy que não está em execução. Inicie o opencodex ou desative a primeira parte do Desktop/CLI para remover as configurações antes de usar o claude puro.", + "claude.firstParty.notApplied": "A primeira parte da CLI está ativada, mas suas configurações de proxy não foram aplicadas. Verifique o env das configurações do Claude antes de usar o claude puro.", + "claude.firstParty.brokenProxy": "Não foi possível iniciar a interceptação. Tente novamente após verificar a configuração do proxy local.", + "claude.firstParty.residual": "As configurações do Claude ainda apontam para um proxy do OpenCodex, embora nem a primeira parte do Desktop nem a da CLI esteja ativada. Remova a configuração de proxy restante ou execute `ocx ensure`.", + "claude.firstParty.routingOff": "O opencodex não está atendendo à interceptação do Claude na configuração atual (o roteamento do Claude ou a interceptação está desativado, ou esta máquina é cliente de outro hub opencodex), então estas configurações apontam para um proxy que não vai atendê-las. Reative-o nesta máquina ou desative a primeira parte para remover as configurações.", + "claude.firstParty.disabled": "Ative primeiro o roteamento e a interceptação do Claude.", + "claude.firstParty.foreign": "O ~/.claude/settings.json aponta para o proxy do opencodex, mas confia em um certificado que o opencodex não gerencia, então as requisições falham. Corrija HTTPS_PROXY / NODE_EXTRA_CA_CERTS manualmente ali.", + "claude.firstParty.local": "O ~/.claude/settings.json envia o Claude Code por um proxy local em 127.0.0.1 que o opencodex não consegue confirmar como seu. Se você não o usa mais, remova HTTPS_PROXY dali.", + "claude.firstParty.unknown": "O opencodex não conseguiu determinar se o ~/.claude/settings.json ainda aponta para o seu proxy.", + "claude.firstParty.refusal.interceptDisabled": "O proxy de interceptação está desativado.", + "claude.firstParty.refusal.interceptUnavailable": "Não foi possível iniciar a interceptação. Tente novamente após verificar a configuração do proxy local.", + "claude.firstParty.refusal.foreignEnv": "Outro programa é dono das configurações de proxy do Claude.", + "claude.firstParty.refusal.caUnavailable": "A autoridade certificadora local está indisponível.", + "claude.firstParty.refusal.unreadable": "Não foi possível ler as configurações do Claude.", + "claude.firstParty.refusal.writeFailed": "Não foi possível gravar as configurações do Claude.", + "claudeDesktop.mode.firstPartyRisk": "Risco para a conta: a primeira parte envia o tráfego da sua assinatura do Claude por um proxy local de interceptação. A Anthropic pode tratar isso como violação dos termos e suspender a conta. O Gateway é o padrão.", + "claudeDesktop.mode.gatewayHint": "O aplicativo inteiro passa a usar o OpenCodex como gateway. O chat roda localmente; os recursos do claude.ai ficam indisponíveis.", + "claudeDesktop.mode.switchNote": "A troca substitui a configuração do outro modo. Depois, feche completamente e reabra o Desktop.", + "claudeDesktop.firstParty.proxyRunning": "Proxy local em 127.0.0.1:{port}", + "claudeDesktop.firstParty.proxyStopped": "Não foi possível iniciar a interceptação. Tente novamente após verificar a configuração do proxy local. ({port})", + "claudeDesktop.firstParty.interceptDisabled": "O proxy de interceptação está desativado na configuração (claudeCode.intercept.enabled)", + "claudeDesktop.picker.title": "Seletor do Claude Desktop", + "claudeDesktop.picker.hint": "Mostra os modelos do OpenCodex no seletor da aba Code do Desktop por meio do proxy local dedicado.", + "claudeDesktop.picker.toggle": "Ativar o seletor do Claude Desktop", + "claudeDesktop.picker.state.active": "O seletor está ativo", + "claudeDesktop.picker.state.restart": "Feche completamente e reabra o Claude Desktop para concluir a aplicação do perfil do seletor.", + "claudeDesktop.picker.state.trustPending": "Aguardando a etapa do chaveiro", + "claudeDesktop.picker.state.trustDeclined": "A confiança no chaveiro foi recusada", + "claudeDesktop.picker.state.proxyUnavailable": "O proxy do seletor não está em execução", + "claudeDesktop.picker.state.unsupported": "O modo seletor não é compatível com este sistema operacional", + "claudeDesktop.picker.state.notFirstParty": "O seletor só está disponível no modo de primeira parte", + "claudeDesktop.picker.state.profileFailed": "Não foi possível aplicar o perfil do seletor", + "claudeDesktop.picker.models": "{count} modelos disponíveis no seletor", + "claudeDesktop.picker.offlineNote": "Com o modo seletor ativado, o Claude Desktop acessa a rede por meio do OpenCodex. Se o OpenCodex parar, o Desktop fica offline até ele reiniciar ou o modo seletor ser desativado.", + "claudeDesktop.firstParty.bindings.title": "Vínculos de modelo da aba Code", + "claudeDesktop.firstParty.bindings.hint": "O Claude Desktop mantém os nomes da Anthropic no seletor da aba Code. Vincule um modelo do seletor a um modelo do OpenCodex e as requisições dessa entrada do seletor serão atendidas pelo modelo vinculado. Somente o tráfego do Claude Code pelo proxy local (a aba Code do Desktop e a CLI claude) usa estes vínculos; o ocx claude não é afetado. Os vínculos valem a partir da próxima requisição — não é preciso fechar e reabrir completamente.", + "claudeDesktop.firstParty.bindings.empty": "Ainda não há vínculos — os modelos do seletor seguem a rota padrão.", + "claudeDesktop.firstParty.bindings.pickerLabel": "Id do modelo no seletor", + "claudeDesktop.firstParty.bindings.pickerPlaceholder": "claude-sonnet-4-6", + "claudeDesktop.firstParty.bindings.routeLabel": "Modelo do OpenCodex", + "claudeDesktop.firstParty.bindings.routePlaceholder": "Escolha um modelo", + "claudeDesktop.firstParty.bindings.add": "Adicionar vínculo", + "claudeDesktop.firstParty.bindings.remove": "Remover vínculo {id}", + "claudeDesktop.firstParty.bindings.saveFailed": "Não foi possível salvar os vínculos de modelo", + "claudeDesktop.health.lastRequest": "Última requisição", + "claudeDesktop.health.stats": "{count} req / {errors} err", + "claudeDesktop.effort.supported": "esforço", + "claudeDesktop.effort.displayOnly": "esforço (apenas exibição)", + "lab.title": "Laboratório de Compatibilidade", + "lab.subtitle": "Matriz de veredictos de compatibilidade somente leitura, a partir de evidências da projeção do laboratório.", + "lab.loadFailed": "Não foi possível carregar os dados do laboratório de compatibilidade", + "lab.projectionUnavailable": "A projeção do laboratório não está disponível. Execute primeiro a conformidade ou as sondagens ao vivo.", + "lab.projectionIncompatible": "O esquema da projeção do laboratório é incompatível. Reconstrua a projeção.", + "lab.statusTitle": "Status da projeção", + "lab.matrixTitle": "Matriz de compatibilidade", + "lab.verdictsTitle": "Registros de veredicto", + "lab.filter.layer": "Camada de evidência", + "lab.filter.verdict": "Veredicto", + "lab.filter.subject": "ID do assunto", + "lab.filter.all": "Todos", + "lab.col.subject": "Assunto", + "lab.col.layer": "Camada", + "lab.col.suite": "Suíte", + "lab.col.verdict": "Veredicto", + "lab.col.asOf": "Data de referência", + "lab.col.protocol": "Conformidade de protocolo", + "lab.col.live": "Compatibilidade de rota ao vivo", + "lab.col.task": "Eficácia na tarefa", + "lab.empty": "Ainda não há veredictos de compatibilidade na projeção.", + "lab.subjectKind": "Tipo", + "lab.observationCount": "Observações", + "lab.eventCount": "Eventos", + "lab.verdictCount": "Veredictos", + "lab.subjectCount": "Assuntos", + "lab.builtAt": "Gerado em", + "lab.loading": "Carregando evidências de compatibilidade…", + "lab.loadMore": "Carregar mais", + "lab.detailTitle": "Detalhe do veredicto", + "lab.detailClose": "Fechar", + "lab.detailSubject": "Assunto", + "lab.detailObservations": "Observações", + "lab.detailEvents": "Eventos contribuintes", + "lab.detailArtifacts": "Metadados dos artefatos", + "lab.production.title": "Tráfego de produção observado", + "lab.production.notVerification": "Não é verificação do Laboratório", + "lab.production.attempts": "Tentativas", + "lab.production.successes": "Sucessos", + "lab.production.routeErrors": "Erros de rota", + "lab.production.lastObserved": "Última observação", + "lab.detailLoadFailed": "Não foi possível carregar o detalhe do veredicto", + "lab.refresh": "Atualizar", + "lab.verdict.UNKNOWN": "Desconhecido", + "lab.verdict.CLAIMED": "Declarado", + "lab.verdict.PROBED": "Sondado", + "lab.verdict.VERIFIED": "Verificado", + "lab.verdict.DEGRADED": "Degradado", + "lab.verdict.BLOCKED": "Bloqueado", + "lab.verdict.UNSUPPORTED": "Sem suporte", + "lab.layer.protocol_conformance": "Conformidade de protocolo", + "lab.layer.live_route_compatibility": "Compatibilidade de rota ao vivo", + "lab.layer.task_effectiveness": "Eficácia na tarefa", + "models.newPolicyGlobal": "Novos modelos começam desativados", + "models.newPolicyProvider": "Política de novos modelos", + "models.fastProvider": "Modo rápido", + "models.fastProviderHint": "Consome créditos de uso a 2x o preço", + "models.fastEnabled": "Modo rápido ativado", + "models.fastDisabled": "Modo rápido desativado", + "models.fastSaveFailed": "Não foi possível salvar o modo Fast", + "models.newPolicy_inherit": "Herdar", + "models.newPolicy_off": "Desativado", + "models.newPolicy_on": "Ativado", + "models.newBadge": "NOVO", + "models.newCount": "{count} novos, desativados", + "models.aliases": "Aliases", + "models.aliasesTable": "Tabela de aliases", + "models.aliasPrompt": "Alias do provedor (deixe vazio para limpar)", + "models.modelAliasPrompt": "Alias do modelo (deixe vazio para limpar)", + "models.aliasSaved": "Alias salvo", + "models.aliasConflict": "Esse alias conflita com um nome existente", + "models.editProviderAlias": "Editar alias do provedor", + "models.editModelAlias": "Editar alias do modelo", + "models.useDefaultAliases": "Usar aliases padrão", + "models.useDefaultAliasesGlobal": "Usar aliases padrão globalmente", + "models.aliasAuto": "auto", + "models.aliasUser": "usuário", + "models.aliasStale": "obsoleto", + "connection.discovering": "Descobrindo destinos locais e compartilhados…", + "connection.machineUnavailable": "O plano da máquina local está indisponível. As requisições compartilhadas não foram redirecionadas para o local.", + "connection.disconnect": "Desconectar do hub", + "connection.disconnectConfirm": "Desconectar esta máquina do hub e reiniciá-la no modo standalone?", + "connection.pairing.hub": "Hub", + "connection.pairing.getCode": "Execute este comando no hub para este navegador:", + "connection.pairing.askOperator": "Se outra pessoa opera o hub, envie a ela este comando e peça um código de pareamento de uso único.", + "connection.pairing.notApiKey": "Cole aqui um código de pareamento de uso único. Chaves de API de dados e tokens de administrador não devem ser colocados neste campo.", + "connection.pairing.networkError": "Não foi possível alcançar o hub. Verifique a conexão e tente novamente; seu código continua aqui.", + "connection.pairing.requestError": "O hub não conseguiu concluir a solicitação de pareamento. Verifique o status dele e tente novamente.", + "connection.pairing.responseError": "O hub não retornou uma sessão de navegador válida. Atualize o hub ou fale com o operador dele e tente novamente.", + "dash.authRequired": "É necessária autenticação do navegador para ver este dashboard.", + "dash.permissionDenied": "Este navegador não tem permissão para ler o dashboard. Verifique as configurações de acesso com o operador do servidor.", + "dash.dataUnavailable": "Não foi possível carregar os dados do dashboard. Verifique a conexão e tente novamente.", + "dash.staleData": "Exibindo os últimos dados recebidos; podem estar desatualizados.", + "connection.pairing.title": "Conectar este dashboard ao hub", + "connection.pairing.body": "Cole o código de pareamento de uso único criado no hub.", + "connection.pairing.relayWarning": "Este código é trocado por meio do relay fixo do hub. O relay não pode ser redirecionado para outro host.", + "connection.pairing.code": "Código de pareamento de uso único", + "connection.pairing.submit": "Conectar", + "connection.pairing.submitting": "Conectando…", + "connection.pairing.error": "O código de pareamento foi recusado ou expirou. O código foi mantido para que você possa conferi-lo.", + "connection.machine.title": "Esta máquina", + "connection.machine.shimHealthy": "O shim do Codex está saudável.", + "connection.machine.shimNeedsAttention": "O shim do Codex precisa de atenção.", + "connection.machine.repairShim": "Reparar shim", + "connection.machine.removeShim": "Remover shim", + "connection.clients.title": "Clientes conectados", + "connection.clients.none": "Nenhum status de cliente disponível", + "connection.clients.sync": "Sincronizar agora", + "connection.clients.syncing": "Sincronizando…", + "connection.sessionLogout": "Encerrar sessão remota", + "connection.sessionLoggingOut": "Encerrando sessão remota…", + "connection.sessionLogoutFailed": "Não foi possível encerrar a sessão remota. A sessão atual foi mantida.", + "usage.source.connected": "Fonte: uso do hub", + "usage.source.local": "Fonte: usage.jsonl local", + "usage.scope.label": "Escopo de uso", + "usage.scope.machine": "Esta máquina", + "usage.scope.hub": "Todo o hub", + "usage.hubOffline": "O uso do hub está indisponível. O uso local não foi usado como substituto.", + "models.displayNameSavedRefreshFailed": "A alteração foi salva, mas não foi possível atualizar a lista de modelos. Tente novamente para atualizá-la.", + "models.displayNameOutcomeUnknown": "A requisição não foi concluída. A alteração pode ter sido salva. Tente novamente para verificar o nome atual antes de fazer outra alteração.", + "models.displayNameCurrentUnavailable": "Nome atual indisponível até a atualização", + "models.displayNameReloaded": "Lista de modelos atualizada", + "models.displayNameAction": "Nome", + "models.displayNameActionLabel": "Editar nome amigável de {model}", + "models.displayNameTitle": "Nome amigável", + "models.displayNameModelId": "ID do modelo", + "models.displayNameCurrent": "Nome atual", + "models.displayNameSourceOperator": "Seu nome", + "models.displayNameSourceProvider": "Nome do provedor", + "models.displayNameSourceFallback": "Fallback para o ID do modelo", + "models.displayNameField": "Nome amigável", + "models.displayNamePlaceholder": "ex.: Grok 4.6", + "models.displayNameHelp": "Altera apenas a apresentação. O roteamento continua {model}.", + "models.displayNameReset": "Redefinir nome", + "models.displayNameSaved": "Nome de exibição salvo", + "models.displayNameResetDone": "Nome de exibição redefinido", + "models.displayNameSaveFailed": "Falha ao salvar o nome de exibição", + "models.displayNameRequired": "Informe um nome amigável ou use Redefinir nome.", + "models.displayNameTooLong": "O nome amigável deve ter no máximo 128 caracteres.", + "models.displayNameNoSlash": "O nome amigável não pode conter /.", + "models.displayNameNoControl": "O nome amigável não pode conter caracteres de controle.", + "pricing.override.action": "Preço", + "pricing.override.actionLabel": "Editar preço de {model}", + "pricing.override.badge": "Preço manual", + "pricing.override.title": "Preço do modelo", + "pricing.override.modelId": "ID do modelo", + "pricing.override.help": "USD por 1M de tokens. Informe as taxas de entrada e saída; taxas de cache em branco usam 0. As quatro taxas em 0 significam gratuito.", + "pricing.override.input": "Entrada", + "pricing.override.output": "Saída", + "pricing.override.cacheRead": "Leitura de cache", + "pricing.override.cacheWrite": "Escrita de cache", + "pricing.override.loading": "Carregando preço salvo…", + "pricing.override.loadFailed": "Não foi possível carregar o preço salvo. Recarregue para tentar de novo.", + "pricing.override.outcomeUnknown": "A requisição não terminou de forma confiável. O preço pode ter mudado. Recarregue o preço salvo antes de editar novamente.", + "pricing.override.recoveryFailed": "Não foi possível recuperar o preço salvo. A edição continua bloqueada; recarregue para tentar de novo.", + "pricing.override.recovered": "Último preço salvo carregado. A requisição anterior ou outro cliente ainda pode alterá-lo.", + "pricing.override.refreshFailed": "O preço foi salvo, mas não foi possível atualizar a lista de modelos. Tente atualizar a lista novamente.", + "pricing.override.invalid": "Informe as taxas de entrada e saída. Cada taxa deve ser um número finito de 0 a 1.000.000.", + "pricing.override.reset": "Voltar ao automático", + "pricing.override.save": "Salvar", + "pricing.override.saving": "Salvando…", + "pricing.override.reload": "Recarregar preço", + "pricing.override.refresh": "Atualizar lista", + "pricing.override.cancel": "Cancelar", + "pricing.override.close": "Fechar", + "usage.range.custom": "Intervalo de datas personalizado", + "usage.range.start": "Início (hora local)", + "usage.range.end": "Fim (hora local)", + "usage.range.apply": "Aplicar", + "usage.range.clear": "Limpar", + "usage.range.help": "Hora local. Inclui o minuto final inteiro.", + "usage.range.required": "Informe a data e hora de início e de fim.", + "usage.range.invalid": "Informe datas e horas locais válidas, a partir de 01/01/1970 UTC.", + "usage.range.reversed": "O fim deve ser igual ou posterior ao início.", + "usage.range.applied": "Intervalo selecionado: {start} – {end} (ambos inclusivos).", + "models.pickerOrder.editorHint": "Reordene os modelos roteados e salve seu rascunho. As linhas em destaque são fixas; modelos nativos não são exibidos.", + "models.pickerOrder.nativeLocked": "Esta ordem salva inclui modelos nativos. Aplique um preset roteado ou Padrão antes de editar o Personalizado.", + "models.pickerOrder.unknownChosen": "As escolhas em destaque são desconhecidas. Recarregue antes de editar.", + "models.pickerOrder.changed": "As configurações do seletor mudaram. Seu rascunho foi mantido; recarregue para descartá-lo e usar as configurações atuais.", + "models.pickerOrder.savedReload": "Ordem salva. Recarregue as configurações atuais antes de editar novamente.", + "models.pickerOrder.requestFailed": "A requisição falhou. Seu rascunho foi mantido; tente novamente ou recarregue.", + "models.pickerOrder.empty": "Nenhum modelo roteado disponível.", + "models.pickerOrder.dragModel": "Arrastar {model}", + "models.pickerOrder.featured": "Em destaque", + "models.pickerOrder.upModel": "Mover {model} para cima", + "models.pickerOrder.downModel": "Mover {model} para baixo", + "models.pickerOrder.position": "{model}: posição {position} de {total}", + "models.pickerOrder.saveDraft": "Salvar rascunho", + "models.pickerOrder.reloadDraft": "Recarregar e descartar rascunho", + "models.pickerOrder.catalogRequired": "As identidades dos modelos estão ausentes ou ambíguas. Recarregue a página Modelos para atualizar o catálogo antes de editar o Personalizado.", + "nav.remote": "Remote Link", + "nav.remoteWorkspace": "Workspace remoto", + "link.title": "Remote Link", + "link.subtitle": "Conecte outro computador com OpenCodex por SSH.", + "link.switch": "Vinculação remota", + "link.switchOffHint": "Ative a vinculação para escolher a função deste computador.", + "link.role.title": "Escolha a função deste computador", + "link.role.hint": "O Home compartilha os provedores deste computador com computadores Child. O Child conecta este computador a um Home quando ele roda no modo standalone.", + "link.role.home": "Home", + "link.role.homeHint": "Gerencie computadores Child a partir deste dashboard.", + "link.role.child": "Child", + "link.role.childHint": "Use a configuração do OpenCodex de outro computador.", + "link.continue": "Continuar", + "link.sessionRequired": "Entre na sessão local do dashboard para gerenciar vínculos.", + "link.workspaceUnavailable": "O Remote Workspace não está disponível nesta instalação.", + "link.loading": "Carregando…", + "link.loadFailed": "Não foi possível carregar o status do Remote Link.", + "link.refresh": "Atualizar", + "link.children": "Computadores Child", + "link.addChild": "Adicionar Child", + "link.sheetTitle": "Adicionar um computador Child", + "link.candidates": "Hosts SSH", + "link.setup.relationship": "O Child usará os provedores OpenCodex deste Home por SSH.", + "link.setup.requirements": "Requer SSH por chave do Home ao Child, OpenCodex 2.66.0+ no Child e macOS ou Linux em ambos os computadores.", + "link.setup.emptyHome": "Os aliases vêm de ~/.ssh/config deste Home. Adicione uma entrada Host e busque novamente, ou informe um alias existente abaixo.", + "link.setup.emptyLocal": "Os aliases vêm de ~/.ssh/config deste computador. Adicione uma entrada Host e busque novamente, ou informe um alias existente abaixo.", + "link.setup.guide": "Guia de configuração do Remote Link", + "link.discoveryFailed": "Não foi possível carregar os hosts SSH", + "link.rescan": "Buscar hosts novamente", + "link.aliasRequired": "Selecione ou informe um alias para habilitar Testar conexão.", + "link.probeHelp": "Confira o motivo acima. Para problemas de acesso SSH, execute isto no terminal deste computador, substituindo pelo alias SSH, e tente novamente.", + "link.noCandidates": "Nenhum host SSH candidato foi encontrado.", + "link.alias": "Alias do host SSH", + "link.aliasPlaceholder": "Informe um alias da sua configuração SSH", + "link.probe": "Testar conexão", + "link.probing": "Testando conexão…", + "link.hostFingerprint": "Impressão digital da chave do host", + "link.confirmFingerprint": "Reconheço esta chave do host", + "link.confirming": "Confirmando…", + "link.confirm": "Confirmar host", + "link.ocxVersion": "Versão do OpenCodex {version}", + "link.apply": "Conectar Child", + "link.applying": "Conectando…", + "link.retry": "Tentar novamente", + "link.disconnect": "Desconectar", + "link.disconnectConfirm": "Desconectar {alias}? A chave de vínculo dele será revogada.", + "link.close": "Fechar", + "link.cancel": "Cancelar", + "remoteLink.childDisabled": "Vínculos Child só podem ser iniciados a partir de um runtime standalone.", + "remoteLink.findHome.title": "Encontrar Home", + "remoteLink.findHome.body": "Escolha o computador Home ao qual conectar este Child.", + "remoteLink.findHome.action": "Encontrar Home", + "remoteLink.findHome.connect": "Conectar como Child", + "remoteLink.findHome.notice": "Conectar reinicia o OpenCodex neste computador. Os turnos do Codex em andamento terminam primeiro, e novas requisições podem falhar por até um minuto. Depois disso, o Codex mantém o mesmo endereço local, e o Home o atende com os próprios provedores e contas.", + "remoteLink.findHome.empty": "Ainda não conectado a um Home.", + "remoteLink.joining": "Entrando no Home…", + "remoteLink.restart.title": "Este computador será reiniciado para se conectar como Child.", + "remoteLink.restart.body": "Os turnos do Codex em execução terminam primeiro; depois o OpenCodex é reiniciado como Child. Esta página recarrega sozinha quando o Child estiver pronto.", + "remoteLink.restart.waiting": "Aguardando este computador se reconectar como Child…", + "remoteLink.restart.slow": "A reinicialização está demorando mais que o normal. Esta página continua verificando e recarrega sozinha assim que o Child responder. Se o OpenCodex parou, inicie-o novamente.", + "remoteLink.error.standalone_required": "Vínculos Child só podem ser iniciados a partir de um runtime standalone.", + "remoteLink.error.join_tunnel_failed": "Não foi possível iniciar o túnel para o Home. Verifique o acesso SSH e tente novamente.", + "remoteLink.error.admission_failed": "O Home não aceitou o novo vínculo. Verifique se ele está em execução e tente novamente.", + "remoteLink.error.join_issue_failed": "O Home não conseguiu emitir um vínculo. Verifique se o OpenCodex está em execução no Home e tente novamente.", + "remoteLink.error.join_in_progress": "Não foi possível concluir a solicitação de vínculo remoto.", + "remoteLink.error.join_port_failed": "Não foi possível concluir a solicitação de vínculo remoto.", + "remoteLink.error.join_port_mismatch": "O OpenCodex não está rodando na porta configurada, por isso não pode reiniciar como Child. Reinicie o OpenCodex na porta configurada e tente novamente.", + "remoteLink.joinDenied.pairing_required": "Emparelhe este computador primeiro para conectá-lo como Child. Abra o painel deste computador pelo endereço de loopback e insira um código de emparelhamento de uso único criado pelo operador.", + "remoteLink.joinDenied.unavailable": "Não é possível conectar como Child nesta sessão do painel. Atualize o status e verifique as configurações de emparelhamento e de runtime deste computador.", + "remoteLink.error.join_rollback_failed": "A entrada falhou e o vínculo no Home não pôde ser removido. Tente a limpeza novamente ou execute ocx link revoke no Home.", + "remoteLink.error.join_restart_failed": "O vínculo está pronto. Reinicie o OpenCodex neste computador para concluir a conexão como Child.", + "remoteLink.error.join_connect_failed": "Não foi possível concluir a solicitação de vínculo remoto.", + "link.noChildren": "Nenhum computador Child conectado.", + "remoteLink.status.connecting": "Conectando", + "remoteLink.status.connected": "Conectado", + "remoteLink.status.reconnecting": "Reconectando", + "remoteLink.status.failed": "A conexão precisa de atenção", + "remoteLink.status.idle": "Ocioso", + "remoteLink.role.standalone": "Independente", + "remoteLink.role.home": "Home", + "remoteLink.role.child": "Child", + "remoteLink.direction.hub": "Iniciado pelo hub", + "remoteLink.direction.client": "Iniciado pelo cliente", + "remoteLink.error.admission_timeout": "Não foi possível concluir a solicitação de vínculo remoto.", + "remoteLink.error.compensation_failed": "Não foi possível concluir a solicitação de vínculo remoto.", + "remoteLink.error.fingerprint_failed": "Não foi possível ler a chave do host SSH. Teste a conexão novamente.", + "remoteLink.error.forbidden": "Esta sessão do dashboard não pode gerenciar vínculos remotos. Abra o dashboard neste computador (instalação standalone) ou use uma sessão de Hub pareada.", + "remoteLink.error.host_confirmation_expired": "Não foi possível concluir a solicitação de vínculo remoto.", + "remoteLink.error.host_fingerprint_mismatch": "Não foi possível concluir a solicitação de vínculo remoto.", + "remoteLink.error.host_not_confirmed": "Não foi possível concluir a solicitação de vínculo remoto.", + "remoteLink.error.invalid_alias": "Não foi possível concluir a solicitação de vínculo remoto.", + "remoteLink.error.invalid_body": "Não foi possível concluir a solicitação de vínculo remoto.", + "remoteLink.error.invalid_link_id": "Não foi possível concluir a solicitação de vínculo remoto.", + "remoteLink.error.key_issue_failed": "Não foi possível concluir a solicitação de vínculo remoto.", + "remoteLink.error.key_revoke_failed": "Não foi possível concluir a solicitação de vínculo remoto.", + "remoteLink.error.link_apply_failed": "Não foi possível concluir a solicitação de vínculo remoto.", + "remoteLink.error.link_exists": "Não foi possível concluir a solicitação de vínculo remoto.", + "remoteLink.error.link_not_found": "Não foi possível concluir a solicitação de vínculo remoto.", + "remoteLink.error.link_remove_failed": "Não foi possível concluir a solicitação de vínculo remoto.", + "remoteLink.error.link_unavailable": "O Remote Link não está disponível neste runtime. Reinicie o OpenCodex e tente novamente.", + "remoteLink.error.listener_unavailable": "O listener do Remote Link não pôde ser iniciado neste computador. Verifique se há conflito de porta e tente novamente.", + "remoteLink.error.probe_failed": "Não foi possível conectar ao host SSH. Verifique se ele aceita sua chave SSH e se o auxiliar de ProxyCommand está instalado.", + "remoteLink.error.remote_connect_failed": "O computador remoto não conseguiu se conectar a este Home. Verifique o serviço OpenCodex dele e tente novamente.", + "remoteLink.error.remote_disconnect_failed": "Não foi possível concluir a solicitação de vínculo remoto.", + "remoteLink.error.remote_ocx_missing": "O ocx não foi encontrado no computador remoto. Instale o OpenCodex lá ou garanta que o ocx esteja no PATH das sessões SSH não interativas.", + "remoteLink.error.remote_ocx_outdated": "O OpenCodex no computador remoto é antigo demais para o Remote Link. Execute ocx update lá (2.66.0 ou posterior) e tente novamente.", + "remoteLink.error.remote_ocx_unrecognized": "O computador remoto não informou uma versão do OpenCodex. O Remote Link exige o OpenCodex 2.66.0 ou posterior no macOS ou Linux.", + "remoteLink.error.remote_port_failed": "O computador remoto não conseguiu informar a porta de vínculo. Verifique se o OpenCodex 2.66.0 ou posterior está em execução lá e tente novamente.", + "remoteLink.error.tailscale_session_refused": "Não foi possível concluir a solicitação de vínculo remoto.", + "remoteLink.error.version_probe_failed": "Não foi possível executar o ocx no computador remoto por SSH. Verifique o acesso SSH e tente novamente.", + "remoteLink.error.generic": "Não foi possível concluir a solicitação de vínculo remoto.", + "remoteLink.reason.auth": "Falha na autenticação.", + "remoteLink.reason.hostkey": "Não foi possível verificar a chave do host.", + "remoteLink.reason.forward": "Falha no encaminhamento de porta.", + "remoteLink.reason.timeout": "A conexão expirou.", + "remoteLink.reason.bind": "Não foi possível associar a porta local.", + "remoteLink.reason.persist": "Não foi possível salvar o estado do vínculo.", + "remoteLink.reason.compensation_failed": "A limpeza após a vinculação falhou.", + "remoteLink.reason.staleTunnel": "Um túnel obsoleto ainda pode estar mantendo a porta.", + "remoteLink.reason.generic": "Motivo:", + "remoteLink.workspaceMoved.title": "O Remote Workspace agora tem página própria", + "remoteLink.workspaceMoved.body": "Use a página separada do Remote Workspace.", + "remoteLink.workspaceMoved.open": "Abrir Remote Workspace", + "remoteLink.forceRemove.title": "Esta máquina não conseguiu alcançar {alias}", + "remoteLink.forceRemove.body": "Remover o vínculo somente nesta máquina? Depois, execute {cmd} você mesmo em {alias}.", + "remoteLink.forceRemove.confirm": "Remover somente aqui", + "remote.title": "Workspace remoto", + "remote.subtitle": "Execute Codex, Claude Code ou Pi a partir deste Hub enquanto arquivos, comandos, testes e builds permanecem no computador selecionado.", + "remote.loading": "Carregando Remote Workspace…", + "remote.loadFailed": "Não foi possível carregar o Remote Workspace.", + "remote.hubRequired": "Use o modo Hub e inicie o Hub com OCX_REMOTE_WORKSPACE_ENABLED=1 para ativar o Remote Workspace.", + "remote.refresh": "Atualizar", + "remote.addComputer": "Adicionar um computador", + "remote.addComputerHint": "Aprove uma ou mais pastas localmente e mantenha o executor exclusivo do OCX conectado a este Hub.", + "remote.createPairing": "Criar código de pareamento", + "remote.pairingCode": "Código de pareamento de uso único", + "remote.pairingExpires": "Expira às {time}", + "remote.pairingCommand": "Execute no computador que você está adicionando", + "remote.pairingCommandPosix": "Terminal Linux / macOS", + "remote.pairingCommandWindows": "Windows PowerShell", + "remote.copyCommand": "Copiar comando", + "remote.copied": "Copiado", + "remote.devices": "Computadores", + "remote.noDevices": "Nenhum computador pareado ainda.", + "remote.online": "Online", + "remote.offline": "Offline", + "remote.revoke": "Revogar computador", + "remote.revokeConfirm": "Revogar {name}? As sessões ativas neste computador serão interrompidas.", + "remote.newSession": "Nova sessão remota", + "remote.device": "Computador", + "remote.folder": "Pasta do workspace", + "remote.runtime": "Agente de código", + "remote.access": "Acesso ao workspace", + "remote.access.readOnly": "Somente leitura", + "remote.access.workspace": "Editar arquivos e executar comandos", + "remote.access.workspaceFilesOnly": "Somente editar arquivos", + "remote.unavailable": "Indisponível", + "remote.capability.full": "Arquivos + comandos em sandbox", + "remote.capability.files": "Somente ferramentas de arquivo", + "remote.runsOnHub": "O modelo e o login permanecem neste Hub", + "remote.runsReadOnly": "Os arquivos só podem ser lidos neste computador", + "remote.runsFilesCommands": "Arquivos, builds e comandos são executados aqui", + "remote.runsFilesOnly": "As ferramentas de arquivo rodam aqui; sandbox de comandos indisponível", + "remote.execUnavailable": "Este computador pode editar arquivos, mas builds e comandos de terminal estão desativados porque não há um sandbox de SO compatível.", + "remote.notResumable": "Esta sessão parou antes de o agente de código criar um histórico durável. Inicie uma nova sessão remota.", + "remote.startSession": "Iniciar sessão remota", + "remote.sessionStarted": "A sessão remota está pronta.", + "remote.sessions": "Sessões", + "remote.noSessions": "Escolha um computador online, uma pasta e um agente de código para começar.", + "remote.events": "Atividade da sessão remota", + "remote.noEvents": "Nenhuma atividade ainda.", + "remote.prompt": "Mensagem", + "remote.promptPlaceholder": "Peça ao agente do Hub para trabalhar dentro da pasta remota selecionada…", + "remote.send": "Enviar", + "remote.stop": "Parar sessão", + "remote.requestFailed": "A requisição do Remote Workspace falhou.", + "remote.submissionUnknown": "O status do envio é desconhecido. Verifique a sessão antes de enviar novamente.", + "remote.status.starting": "Iniciando", + "remote.status.ready": "Pronto", + "remote.status.running": "Em execução", + "remote.status.waiting": "Executor offline", + "remote.status.failed": "Falhou", + "remote.status.stopped": "Parado", + "remote.event.status": "Status", + "remote.event.tool": "Ferramenta remota", + "remote.event.error": "Erro", + "quotaSummary.aria": "Resumo de cota dos provedores", + "quotaSummary.updated": "Atualizado {time}", + "quotaSummary.dataAt": "Dados de {time}", + "quotaSummary.observedAt": "Observado em {time}", + "quotaSummary.warn": "70%+ usado", + "quotaSummary.critical": "90%+ usado", + "quotaSummary.credits": "Créditos", + "quotaSummary.refreshFailed": "A última atualização falhou; exibindo a leitura anterior", + "quotaSummary.scrollPrev": "Rolar provedores para a esquerda", + "quotaSummary.scrollNext": "Rolar provedores para a direita", + "quotaSummary.openAccounts": "Abrir gerenciamento de contas", + "pws.protocol.title": "Wire upstream", + "pws.protocol.note": "O formato de API que o opencodex usa para falar com este provedor. Ele não ativa nem desativa nenhuma API de cliente; a página de API controla isso.", + "pws.protocol.adapterLabel": "Wire upstream que este provedor recebe", + "pws.protocol.source": "Definido por", + "pws.protocol.source.hardPin": "Fixado pelo opencodex para este modelo", + "pws.protocol.source.operator": "Sua configuração", + "pws.protocol.source.registry": "Catálogo de provedores", + "pws.protocol.source.providerDefault": "Padrão do provedor", + "pws.protocol.pending": "Depois de salvar, este provedor receberá {adapter}.", + "pws.protocol.overrides": "Modelos em outro wire", + "pws.protocol.noOverrides": "Todos os modelos usam o wire do provedor.", + "pws.protocol.overridesTruncated": "Exibindo os primeiros {count} modelos.", + "pws.protocol.col.model": "Modelo", + "pws.protocol.col.wire": "Wire", + "pws.protocol.col.source": "Definido por", + "pws.protocol.loadFailed": "Não foi possível carregar o wire upstream.", + "compatProtocol.filter.inbound": "API do cliente", + "compatProtocol.filter.upstream": "Wire upstream", + "compatProtocol.anyProtocol": "qualquer", + "compatProtocol.pair": "{inbound} → {upstream}", + "compatProtocol.loading": "Lendo o par de protocolos de cada subject…", + "compatProtocol.unverified": "Ainda não há evidência do Lab para {pair}. O par não foi verificado, não que tenha falhado.", + "compatProtocol.unresolved": "Não foi possível ler o par de protocolos de {count} subjects; eles ficam fora deste filtro.", + "compatProtocol.axisNote": "Estes são apenas veredictos do Lab. A forma como uma requisição é entregue (nativa ou traduzida) é mostrada na prévia do caminho da requisição, na página de API.", + "protocolLinks.labEvidence": "Evidência do Lab para este par", + "protocolLinks.providerSettings": "Configurações do provedor", + "cws.plan.title": "Caminhos candidatos", + "cws.plan.description": "Pergunta ao proxy como cada destino carregaria uma requisição com todos os recursos que a API do cliente consegue expressar, e quais recursos todos os destinos preservam. Nada é enviado ao upstream.", + "cws.plan.run": "Mostrar caminhos candidatos", + "cws.plan.savedOnly": "Mostra o combo salvo. Salve suas alterações para visualizá-las.", +}; diff --git a/gui/src/i18n/routing-compatibility-labels.ts b/gui/src/i18n/routing-compatibility-labels.ts index c4e3de76b6b..42b8d75a8ad 100644 --- a/gui/src/i18n/routing-compatibility-labels.ts +++ b/gui/src/i18n/routing-compatibility-labels.ts @@ -63,4 +63,9 @@ export const ROUTING_COMPATIBILITY_FIELD_LABELS: Record = { "theme.light": "Светлая", "theme.dark": "Тёмная", "theme.system": "Системная", + "zoom.label": "Масштаб", + "zoom.out": "Уменьшить", + "zoom.in": "Увеличить", + "zoom.reset": "Сбросить масштаб до 100 %", "lang.label": "Язык", "lang.nativeName": "Русский", "provider.name.commandCodeAuth": "Command Code - Auth", @@ -444,14 +448,25 @@ export const ru: Record = { "compactionRouting.triggersManual": "Только ручной /compact", "compactionRouting.triggersBoth": "Ручное и автоматическое", "compactionRouting.triggersAuto": "Только автоматическое", + "compactionRouting.sources": "Источники", + "compactionRouting.sourcesAll": "Все модели диалога", + "compactionRouting.sourcesSelected": "Только выбранные источники", + "compactionRouting.sourcesProviders": "Провайдеры целиком", + "compactionRouting.sourcesModels": "Отдельные модели", + "compactionRouting.sourcesSaved": "Сохранённые селекторы вне текущего каталога", + "compactionRouting.sourcesNone": "Выберите хотя бы один источник или вернитесь ко всем моделям диалога.", + "compactionRouting.sourcesHint": "Применяет переопределение, только если исходная модель запроса сжатия совпадает с выбранной моделью или provider/*. Остальные запросы используют свою модель.", + "compactionRouting.sourcesGridTitle": "Перенаправлять запросы сжатия, исходная модель которых совпадает с:", "compactionRouting.currentModel": "Использовать модель разговора", "compactionRouting.currentEffort": "Сохранить уровень из запроса", "compactionRouting.effortHint": "Уровень рассуждений применяется, если его поддерживает конечная точка сжатия. Модель должна вмещать весь разговор.", "compactionRouting.dataNotice": "Охваченный запрос сжатия отправляет весь разговор провайдеру выбранной модели для составления сводки, даже если разговор идёт у другого провайдера.", "compactionRouting.autoNotice": "Автоматическое сжатие запускается само, поэтому длинный разговор может уйти этому провайдеру без вашего запроса.", "compactionRouting.providerWarning": "С этой настройкой каждый охваченный запрос сжатия отправляет полное содержимое разговора провайдеру {provider} для составления сводки.", - "compactionRouting.comboWarning": "С этой настройкой каждый охваченный запрос сжатия отправляет полное содержимое разговора комбо {combo} для составления сводки. Комбо пробует свои цели ({providers}) по порядку и использует первую ответившую, поэтому разговор может получить любая из них.", + "compactionRouting.comboWarning": "С этой настройкой каждый охваченный запрос сжатия отправляет полное содержимое разговора комбо {combo} для составления сводки. Комбо выбирает одну из целей ({providers}) по своей стратегии маршрутизации, но после сбоя, допускающего повторную попытку, может повторить тот же запрос с полным содержимым разговора на другой цели, поэтому одна или несколько целей могут получить полное содержимое разговора.", "compactionRouting.comboProvidersUnknown": "его настроенные целевые провайдеры", + "compactionRouting.providerWarningScoped": "При этой настройке только запросы сжатия, исходная модель которых совпадает с {sources}, отправляют полное содержимое диалога в {provider} для составления сводки; остальные запросы используют свою модель.", + "compactionRouting.comboWarningScoped": "При этой настройке только запросы сжатия, исходная модель которых совпадает с {sources}, отправляют полное содержимое диалога комбо {combo} для составления сводки; остальные запросы используют свою модель. Комбо выбирает одну из целей ({providers}) по своей стратегии маршрутизации, но после повторяемого сбоя может повторить тот же запрос с полным содержимым разговора на другой цели, поэтому одна или несколько целей могут получить полное содержимое разговора.", "compactionRouting.loadFailed": "Не удалось загрузить настройки сжатия.", "compactionRouting.saved": "Настройки сжатия сохранены.", "compactionRouting.saveFailed": "Не удалось сохранить. Изменения остались; попробуйте снова.", @@ -882,6 +897,17 @@ export const ru: Record = { "models.selectedCount": "Выбрано: {n}", // subagents + "sub.forceTitle": "Принудительно использовать одну модель для всех субагентов", + "sub.forceModel": "Модель субагентов Claude Code", + "sub.forceChoose": "Выберите модель, доступную через прокси", + "sub.forceHelp": "Применяется к агентам плагинов и встроенным агентам (включая Explore/Plan), переопределяя модель, указанную при каждом вызове. Форки и навыки с model: inherit сохраняют модель основного диалога. Основная модель и вспомогательные модели Haiku/small-fast не затрагиваются.", + "sub.forceScope": "По умолчанию отключено. Применяется со следующего запуска с маршрутизацией через ocx claude, но не при прямом запуске claude. Экспортированные переменные оболочки имеют приоритет; env в settings.json может их переопределить. Статус учитывает только CLI и пользовательские настройки на сервере, но не другую оболочку или настройки проекта.", + "sub.forceInvalid": "Настроенная целевая модель недоступна. Переопределение модели при запуске будет пропущено; выберите модель, доступную через прокси, или отключите эту функцию.", + "sub.forceOld": "Версия Claude Code ниже 2.1.257: FORCE игнорируется; применяется только модель субагентов по умолчанию без принудительного выбора.", + "sub.forceUnknown": "Версия Claude Code неизвестна. Для принудительного выбора модели требуется версия 2.1.257 или новее.", + "sub.forceOverride": "Переопределено через settings.json: его env содержит модель субагента или настройку FORCE. OpenCodex не изменяет этот файл.", + "sub.forceSettingsUnknown": "Не удалось проверить settings.json. Неизвестно, переопределяют ли его настройки этот параметр.", + "sub.forceSaved": "Сохранено. Вступит в силу при следующем запуске с маршрутизацией через ocx claude.", "sub.subtitle": "{cmd} в Codex объявляет как переопределения только первые 5 моделей (по приоритету). Выберите здесь до 5 моделей — нативные gpt или маршрутизируемые — и opencodex задаст им приоритет в каталоге так, чтобы именно они шли первыми. Любую другую модель по-прежнему можно вызвать по её точному имени; эта настройка управляет только тем, что отображается.", "sub.featured": "Избранные", "sub.advanced": "Дополнительно", @@ -1028,7 +1054,7 @@ export const ru: Record = { "logs.detail.ttft": "TTFT", "logs.detail.decodeTokPerSec": "Скорость декодирования (оценка)", "logs.detail.reason.ttft_missing": "Для этого запроса не записано время до первого токена, поэтому измерять нечего.", - "logs.detail.reason.decode_window_too_short": "Окно после первого токена длилось меньше секунды — слишком мало для оценки скорости декодирования.", + "logs.detail.reason.decode_window_too_short": "Измеренное окно вывода длилось меньше секунды — слишком мало для оценки скорости декодирования.", "logs.detail.costTotal": "Эквивалент по прайс-листу", "logs.detail.totalTokens": "Всего токенов", "logs.detail.matchedKey": "Совпавший ключ цены", @@ -1288,9 +1314,12 @@ export const ru: Record = { "usage.col.reported": "Сообщено", "usage.col.inputTokens": "Входные токены", "usage.col.outputTokens": "Выходные токены", + "usage.col.tokPerSec": "Выходные tok/s", "usage.col.cacheHits": "Попадания в кэш", "usage.col.cacheWrites": "Записи в кэш", "usage.col.cacheHitRate": "Доля попаданий", + "usage.throughput.title": "Сквозная пропускная способность вывода: сумма выходных токенов, делённая на сумму астрономических длительностей (число измеренных попыток — {samples}). Включает ожидание до декодирования, поэтому ниже установившейся скорости декодирования; запросы без измеренных токенов и длительности исключаются.", + "usage.throughput.unmeasured": "Ни один запрос этой строки не сообщил одновременно токены вывода и длительность — усреднять нечего.", "usage.cacheHitRate.partial": "Среднее рассчитано по {measured} из {total} входных токенов; для остальных запросов сведения о кэше не поступили.", "usage.cacheHitRate.unmeasured": "Ни один запрос в этой строке не содержал сведений о кэше, поэтому среднюю долю попаданий рассчитать нельзя.", "usage.unavailable": "—", @@ -2007,6 +2036,16 @@ export const ru: Record = { "integrations.tab.cline": "Cline CLI", "integrations.tab.kilo": "Kilo", "integrations.tab.droid": "Factory Droid", + "integrations.droidReasoning.title": "Уровень рассуждений по умолчанию", + "integrations.droidReasoning.description": "Значение по умолчанию применяется, только если запрос не задаёт уровень рассуждений. Отключите Factory Droid, чтобы удалить сохранённые значения.", + "integrations.droidReasoning.noDefault": "Без значения по умолчанию", + "integrations.droidReasoning.noEfforts": "Нет доступных вариантов уровня рассуждения", + "integrations.droidReasoning.noModels": "Нет доступных экспортированных моделей.", + "integrations.droidReasoning.modelDefault": "Уровень рассуждений по умолчанию для {model}", + "integrations.droidReasoning.unsupportedTitle": "Некоторые сохранённые значения больше не поддерживаются. Удалите их, затем сохраните / проверьте изменения, чтобы применить их.", + "integrations.droidReasoning.unsupportedEffort": "Неподдерживаемый уровень: {effort}", + "integrations.droidReasoning.clear": "Удалить значение", + "integrations.droidReasoning.saveReview": "Сохранить / проверить изменения", "integrations.aside.profilesTitle": "Профили Aside", "integrations.aside.profilesHint": "Выберите профили, в которые будут добавлены выбранные модели. Активный профиль Aside не изменится.", "integrations.aside.all": "Синхронизировать все профили", @@ -3582,6 +3621,15 @@ export const ru: Record = { "link.addChild": "Добавить дочерний", "link.sheetTitle": "Добавить дочерний компьютер", "link.candidates": "SSH-хосты", + "link.setup.relationship": "Child будет использовать провайдеры OpenCodex этого Home через SSH.", + "link.setup.requirements": "Нужны SSH по ключу от Home к Child, OpenCodex 2.66.0+ на Child и macOS или Linux на обоих компьютерах.", + "link.setup.emptyHome": "Псевдонимы берутся из ~/.ssh/config этого Home. Добавьте запись Host и повторите поиск или введите существующий псевдоним ниже.", + "link.setup.emptyLocal": "Псевдонимы берутся из ~/.ssh/config этого компьютера. Добавьте запись Host и повторите поиск или введите существующий псевдоним ниже.", + "link.setup.guide": "Руководство по настройке Remote Link", + "link.discoveryFailed": "Не удалось загрузить SSH-хосты", + "link.rescan": "Повторить поиск хостов", + "link.aliasRequired": "Выберите или введите псевдоним, чтобы включить проверку соединения.", + "link.probeHelp": "Проверьте причину выше. При проблемах доступа SSH выполните это в терминале этого компьютера, заменив своим SSH-псевдонимом, затем повторите попытку.", "link.noCandidates": "Кандидаты SSH-хостов не найдены.", "link.alias": "Псевдоним SSH-хоста", "link.aliasPlaceholder": "Введите псевдоним из конфигурации SSH", @@ -3620,6 +3668,8 @@ export const ru: Record = { "remoteLink.error.join_restart_failed": "Связь готова. Перезапустите OpenCodex на этом компьютере, чтобы завершить подключение в роли дочернего компьютера.", "remoteLink.error.join_port_failed": "Не удалось завершить запрос удалённой связи.", "remoteLink.error.join_port_mismatch": "OpenCodex работает не на настроенном порту, поэтому не может перезапуститься как Child. Перезапустите OpenCodex на настроенном порту и повторите попытку.", + "remoteLink.joinDenied.pairing_required": "Сначала выполните сопряжение этого компьютера, чтобы подключить его в роли дочернего. Откройте дашборд этого компьютера по loopback-адресу и введите одноразовый код сопряжения, созданный оператором.", + "remoteLink.joinDenied.unavailable": "Подключение в роли дочернего компьютера недоступно в этой сессии дашборда. Обновите статус и проверьте настройки сопряжения и среды выполнения на этом компьютере.", "remoteLink.error.join_connect_failed": "Не удалось завершить запрос удалённой связи.", "link.noChildren": "Дочерние компьютеры не подключены.", "remoteLink.status.connecting": "Подключение", diff --git a/gui/src/i18n/shared.ts b/gui/src/i18n/shared.ts index 30ba5b8c031..e0acadf3f7c 100644 --- a/gui/src/i18n/shared.ts +++ b/gui/src/i18n/shared.ts @@ -14,6 +14,7 @@ export const LOCALES: { code: Locale; htmlLang: string }[] = [ { code: "ja", htmlLang: "ja" }, { code: "tr", htmlLang: "tr" }, { code: "vi", htmlLang: "vi" }, + { code: "pt", htmlLang: "pt-BR" }, ]; const LANG_KEY = "ocx-lang"; @@ -23,7 +24,7 @@ let activeLocale: Locale | null = null; export function detectInitial(): Locale { try { const stored = localStorage.getItem(LANG_KEY); - if (stored === "en" || stored === "de" || stored === "fr" || stored === "ko" || stored === "zh" || stored === "zh-TW" || stored === "ru" || stored === "ja" || stored === "tr" || stored === "vi") return stored; + if (stored === "en" || stored === "de" || stored === "fr" || stored === "ko" || stored === "zh" || stored === "zh-TW" || stored === "ru" || stored === "ja" || stored === "tr" || stored === "vi" || stored === "pt") return stored; } catch { /* ignore */ } const nav = typeof navigator !== "undefined" && navigator?.language ? navigator.language.toLowerCase() : "en"; if (nav.startsWith("de")) return "de"; @@ -45,6 +46,7 @@ export function detectInitial(): Locale { if (nav.startsWith("ja")) return "ja"; if (nav.startsWith("tr")) return "tr"; if (nav.startsWith("vi")) return "vi"; + if (nav.startsWith("pt")) return "pt"; return "en"; } diff --git a/gui/src/i18n/tr.ts b/gui/src/i18n/tr.ts index 596ddc56929..050daecfe11 100644 --- a/gui/src/i18n/tr.ts +++ b/gui/src/i18n/tr.ts @@ -175,6 +175,10 @@ export const tr: Record = { "theme.light": "Açık", "theme.dark": "Koyu", "theme.system": "Sistem", + "zoom.label": "Yakınlaştırma", + "zoom.out": "Uzaklaştır", + "zoom.in": "Yakınlaştır", + "zoom.reset": "Yakınlaştırmayı %100'e sıfırla", "lang.label": "Dil", "lang.nativeName": "Türkçe", "provider.name.commandCodeAuth": "Command Code - Auth", @@ -445,14 +449,25 @@ export const tr: Record = { "compactionRouting.triggersManual": "Yalnızca manuel /compact", "compactionRouting.triggersBoth": "Manuel ve otomatik", "compactionRouting.triggersAuto": "Yalnızca otomatik", + "compactionRouting.sources": "Kaynaklar", + "compactionRouting.sourcesAll": "Tüm konuşma modelleri", + "compactionRouting.sourcesSelected": "Yalnızca seçili kaynaklar", + "compactionRouting.sourcesProviders": "Tüm sağlayıcılar", + "compactionRouting.sourcesModels": "Tekil modeller", + "compactionRouting.sourcesSaved": "Geçerli katalogda olmayan kayıtlı seçiciler", + "compactionRouting.sourcesNone": "En az bir kaynak seçin veya tüm konuşma modellerine dönün.", + "compactionRouting.sourcesHint": "Geçersiz kılmayı yalnızca özetleme isteğinin kaynak modeli seçilen modelle veya provider/* ile eşleştiğinde uygular. Eşleşmeyen istekler kendi modelini kullanır.", + "compactionRouting.sourcesGridTitle": "Kaynak modeli aşağıdakilerle eşleşen özetleme isteklerini yeniden yönlendir:", "compactionRouting.currentModel": "Konuşma modelini kullan", "compactionRouting.currentEffort": "İsteğin düzeyini koru", "compactionRouting.effortHint": "Akıl yürütme, özetleme uç noktası destekliyorsa uygulanır. Model tüm konuşmayı kabul edebilmelidir.", "compactionRouting.dataNotice": "Kapsama giren bir özetleme isteği, konuşma başka bir sağlayıcıda yürütülse bile konuşmanın tamamını özetlenmek üzere seçilen modelin sağlayıcısına gönderir.", "compactionRouting.autoNotice": "Otomatik özetleme kendiliğinden çalışır; bu nedenle uzun bir konuşma siz istemeden o sağlayıcıya gidebilir.", "compactionRouting.providerWarning": "Bu ayarla kapsama giren her özetleme isteği, konuşmanın tüm içeriğini özetlenmek üzere {provider} sağlayıcısına gönderir.", - "compactionRouting.comboWarning": "Bu ayarla kapsama giren her özetleme isteği, konuşmanın tüm içeriğini özetlenmek üzere {combo} kombosuna gönderir. Kombo hedeflerini ({providers}) sırayla dener ve yanıt veren ilkini kullanır; dolayısıyla bunlardan herhangi biri konuşmayı alabilir.", + "compactionRouting.comboWarning": "Bu ayarla kapsama giren her özetleme isteği, konuşmanın tüm içeriğini özetlenmek üzere {combo} kombosuna gönderir. Kombo, yönlendirme stratejisine göre hedeflerinden ({providers}) birini seçer, ancak yeniden denenebilir bir hata sonrasında aynı tam konuşma isteğini başka bir hedefte yeniden deneyebilir; dolayısıyla bu hedeflerden biri veya birden fazlası konuşmayı alabilir.", "compactionRouting.comboProvidersUnknown": "yapılandırılmış hedef sağlayıcıları", + "compactionRouting.providerWarningScoped": "Bu ayarla yalnızca kaynak modeli {sources} ile eşleşen özetleme istekleri konuşmanın tüm içeriğini {provider} hedefine gönderir; eşleşmeyen istekler kendi modelini kullanır.", + "compactionRouting.comboWarningScoped": "Bu ayarla yalnızca kaynak modeli {sources} ile eşleşen özetleme istekleri konuşmanın tüm içeriğini {combo} kombosuna gönderir; eşleşmeyen istekler kendi modelini kullanır. Kombo, yönlendirme stratejisine göre hedeflerinden ({providers}) birini seçer, ancak yeniden denenebilir bir hata sonrasında aynı tam konuşma isteğini başka bir hedefte yeniden deneyebilir; dolayısıyla bu hedeflerden biri veya birden fazlası konuşmayı alabilir.", "compactionRouting.loadFailed": "Özetleme ayarları yüklenemedi.", "compactionRouting.saved": "Özetleme ayarları kaydedildi.", "compactionRouting.saveFailed": "Kaydedilemedi. Değişiklikleriniz korunuyor; tekrar deneyin.", @@ -885,6 +900,17 @@ export const tr: Record = { "models.selectedCount": "{n} seçildi", // subagents + "sub.forceTitle": "Tüm alt ajanları aynı modeli kullanmaya zorla", + "sub.forceModel": "Claude Code alt ajan modeli", + "sub.forceChoose": "Erişime açılmış bir model seçin", + "sub.forceHelp": "Eklenti ajanlarına ve yerleşik ajanlara (Explore/Plan dahil) uygulanır; çağrı başına seçilen modelleri geçersiz kılar. Fork’lar ve model: inherit kullanan beceriler, ana sohbetin modelini kullanmaya devam eder. Ana model ve Haiku/small-fast yardımcı modelleri etkilenmez.", + "sub.forceScope": "Varsayılan olarak kapalıdır. Bir sonraki yönlendirmeli ocx claude başlatmasından itibaren geçerlidir; doğrudan claude çalıştırıldığında uygulanmaz. Kabukta dışa aktarılan ortam değişkenleri önceliklidir; settings.json içindeki env bunları geçersiz kılabilir. Durum denetimi yalnızca sunucudaki CLI ve kullanıcı ayarlarını kapsar; başka bir kabuğu veya proje ayarlarını kapsamaz.", + "sub.forceInvalid": "Yapılandırılan hedef kullanılamıyor. Başlatma sırasında modelin geçersiz kılınması atlanacak; erişime açılmış bir model seçin veya bu özelliği kapatın.", + "sub.forceOld": "Claude Code sürümü 2.1.257’den eski: FORCE yok sayılır; yalnızca zorunlu tutulmayan varsayılan alt ajan modeli uygulanır.", + "sub.forceUnknown": "Claude Code sürümü bilinmiyor. Bir modeli zorunlu tutmak için 2.1.257 veya daha yeni bir sürüm gerekir.", + "sub.forceOverride": "settings.json tarafından geçersiz kılındı: env alanında bir alt ajan modeli veya FORCE ayarı var. OpenCodex bu dosyayı değiştirmez.", + "sub.forceSettingsUnknown": "settings.json incelenemedi. Ayarların bu seçeneği geçersiz kılıp kılmadığı bilinmiyor.", + "sub.forceSaved": "Kaydedildi. Bir sonraki yönlendirmeli ocx claude başlatmasında geçerli olur.", "sub.subtitle": "Codex'in {cmd} komutu, geçersiz kılma olarak yalnızca ilk 5 modeli (önceliğe göre) sunar. Buradan 5 taneye kadar seçin (yerel gpt veya yönlendirilen) ve opencodex bunların katalog önceliğini tam olarak bunların liderlik edeceği şekilde ayarlar. Diğer herhangi bir model tam adıyla çağrılabilir kalır; bu yalnızca neyin gösterileceğini kontrol eder.", "sub.featured": "Öne Çıkarılanlar", "sub.advanced": "Gelişmiş", @@ -1055,7 +1081,7 @@ export const tr: Record = { "logs.detail.ttft": "TTFT", "logs.detail.decodeTokPerSec": "Çözme hızı (tahmini)", "logs.detail.reason.ttft_missing": "Bu istek için ilk token süresi kaydedilmediğinden ölçülecek bir çözme aralığı yok.", - "logs.detail.reason.decode_window_too_short": "İlk token sonrasındaki aralık bir saniyenin altındaydı; çözme hızını tahmin etmek için fazla kısa.", + "logs.detail.reason.decode_window_too_short": "Ölçülen çıktı aralığı bir saniyenin altındaydı; çözme hızını tahmin etmek için fazla kısa.", "logs.detail.costTotal": "Liste fiyatı eşdeğeri", "logs.detail.totalTokens": "Toplam jeton", "logs.detail.matchedKey": "Eşleşen anahtar", @@ -1315,9 +1341,12 @@ export const tr: Record = { "usage.col.reported": "Bildirilen", "usage.col.inputTokens": "Girdi jetonları", "usage.col.outputTokens": "Çıktı jetonları", + "usage.col.tokPerSec": "Çıktı tok/s", "usage.col.cacheHits": "Önbellek isabetleri", "usage.col.cacheWrites": "Önbellek yazma", "usage.col.cacheHitRate": "İsabet oranı", + "usage.throughput.title": "Uçtan uca çıktı verimi: {samples} ölçülen deneme için çıktı jetonları toplamı ÷ geçen süre toplamı. Kod çözme öncesi bekleme süresini içerir, bu yüzden kararlı kod çözme hızının altındadır; ölçülen jetonu ve süresi olmayan istekler hariç tutulur.", + "usage.throughput.unmeasured": "Bu satırdaki hiçbir istek hem çıktı jetonlarını hem de süreyi bildirmedi; ortalanacak verim yok.", "usage.cacheHitRate.partial": "Toplam {total} girdi jetonunun {measured} kadarı üzerinden ortalama hesaplandı; kalan isteklerde önbellek ayrıntısı bildirilmedi.", "usage.cacheHitRate.unmeasured": "Bu satırdaki hiçbir istek önbellek ayrıntısı bildirmediği için hesaplanabilecek bir ortalama isabet oranı yok.", "usage.unavailable": "—", @@ -2034,6 +2063,16 @@ export const tr: Record = { "integrations.tab.cline": "Cline CLI", "integrations.tab.kilo": "Kilo", "integrations.tab.droid": "Factory Droid", + "integrations.droidReasoning.title": "Varsayılan akıl yürütme düzeyi", + "integrations.droidReasoning.description": "Varsayılan yalnızca istek akıl yürütme düzeyi belirtmediğinde kullanılır. Kaydedilmiş varsayılanları kaldırmak için Factory Droid'u devre dışı bırakın.", + "integrations.droidReasoning.noDefault": "Varsayılan yok", + "integrations.droidReasoning.noEfforts": "Kullanılabilir akıl yürütme düzeyi seçeneği yok", + "integrations.droidReasoning.noModels": "Dışa aktarılmış model yok.", + "integrations.droidReasoning.modelDefault": "{model} için varsayılan akıl yürütme düzeyi", + "integrations.droidReasoning.unsupportedTitle": "Kaydedilmiş bazı varsayılanlar artık desteklenmiyor. Bunları temizleyin, ardından uygulamak için değişiklikleri kaydedip inceleyin.", + "integrations.droidReasoning.unsupportedEffort": "Desteklenmeyen düzey: {effort}", + "integrations.droidReasoning.clear": "Varsayılanı temizle", + "integrations.droidReasoning.saveReview": "Değişiklikleri kaydet / incele", "integrations.aside.profilesTitle": "Aside profilleri", "integrations.aside.profilesHint": "Seçili modellerin hangi profillere aktarılacağını seçin. Aside’ın etkin profili değişmez.", "integrations.aside.all": "Tüm profilleri eşitle", @@ -3582,6 +3621,15 @@ export const tr: Record = { "link.addChild": "Çocuk ekle", "link.sheetTitle": "Çocuk bilgisayarı ekle", "link.candidates": "SSH ana bilgisayarları", + "link.setup.relationship": "Child, bu Home bilgisayarının OpenCodex sağlayıcılarını SSH üzerinden kullanır.", + "link.setup.requirements": "Home’dan Child’a anahtar tabanlı SSH, Child üzerinde OpenCodex 2.66.0+ ve her iki bilgisayarda macOS veya Linux gerekir.", + "link.setup.emptyHome": "Takma adlar bu Home bilgisayarının ~/.ssh/config dosyasından gelir. Bir Host girdisi ekleyip yeniden tarayın veya aşağıya mevcut bir takma ad girin.", + "link.setup.emptyLocal": "Takma adlar bu bilgisayarın ~/.ssh/config dosyasından gelir. Bir Host girdisi ekleyip yeniden tarayın veya aşağıya mevcut bir takma ad girin.", + "link.setup.guide": "Remote Link kurulum kılavuzu", + "link.discoveryFailed": "SSH ana bilgisayarları yüklenemedi", + "link.rescan": "Ana bilgisayarları yeniden tara", + "link.aliasRequired": "Bağlantı testini etkinleştirmek için bir takma ad seçin veya girin.", + "link.probeHelp": "Yukarıdaki nedeni kontrol edin. SSH erişim sorunlarında bunu bu bilgisayarın terminalinde çalıştırın; yerine SSH takma adınızı yazıp yeniden deneyin.", "link.noCandidates": "SSH ana bilgisayarı adayı bulunamadı.", "link.alias": "SSH ana bilgisayar takma adı", "link.aliasPlaceholder": "SSH yapılandırmanızdaki takma adı girin", @@ -3620,6 +3668,8 @@ export const tr: Record = { "remoteLink.error.join_restart_failed": "Bağlantı hazır. Çocuk olarak bağlanmayı tamamlamak için bu bilgisayarda OpenCodex'i yeniden başlatın.", "remoteLink.error.join_port_failed": "Uzak bağlantı isteği tamamlanamadı.", "remoteLink.error.join_port_mismatch": "OpenCodex yapılandırılmış bağlantı noktasında çalışmıyor, bu yüzden Çocuk olarak yeniden başlatılamıyor. OpenCodex'i yapılandırılmış bağlantı noktasında yeniden başlatın ve tekrar deneyin.", + "remoteLink.joinDenied.pairing_required": "Çocuk olarak bağlanmak için önce bu bilgisayarı eşleştirin. Bu bilgisayarın geri döngü (loopback) adresindeki gösterge panelini açın ve operatörün oluşturduğu tek kullanımlık eşleştirme kodunu girin.", + "remoteLink.joinDenied.unavailable": "Bu gösterge paneli oturumunda Çocuk olarak bağlanılamıyor. Durumu yenileyin ve bu bilgisayarın eşleştirme ve çalışma zamanı ayarlarını kontrol edin.", "remoteLink.error.join_connect_failed": "Uzak bağlantı isteği tamamlanamadı.", "link.noChildren": "Bağlı çocuk bilgisayarı yok.", "remoteLink.status.connecting": "Bağlanıyor", diff --git a/gui/src/i18n/vi.ts b/gui/src/i18n/vi.ts index fda93dd64fb..3074945f070 100644 --- a/gui/src/i18n/vi.ts +++ b/gui/src/i18n/vi.ts @@ -173,6 +173,10 @@ export const vi: Record = { "theme.light": "Sáng", "theme.dark": "Tối", "theme.system": "Hệ thống", + "zoom.label": "Thu phóng", + "zoom.out": "Thu nhỏ", + "zoom.in": "Phóng to", + "zoom.reset": "Đặt lại thu phóng về 100%", "lang.label": "Ngôn ngữ", "lang.nativeName": "Tiếng Việt", "provider.name.commandCodeAuth": "Command Code - Auth", @@ -443,14 +447,25 @@ export const vi: Record = { "compactionRouting.triggersManual": "Chỉ /compact thủ công", "compactionRouting.triggersBoth": "Thủ công và tự động", "compactionRouting.triggersAuto": "Chỉ tự động", + "compactionRouting.sources": "Nguồn áp dụng", + "compactionRouting.sourcesAll": "Tất cả model hội thoại", + "compactionRouting.sourcesSelected": "Chỉ nguồn đã chọn", + "compactionRouting.sourcesProviders": "Toàn bộ nhà cung cấp", + "compactionRouting.sourcesModels": "Từng model", + "compactionRouting.sourcesSaved": "Bộ chọn đã lưu không có trong danh mục hiện tại", + "compactionRouting.sourcesNone": "Chọn ít nhất một nguồn, hoặc quay lại tất cả model hội thoại.", + "compactionRouting.sourcesHint": "Chỉ áp dụng ghi đè khi model nguồn của yêu cầu nén khớp với model đã chọn hoặc provider/*. Các yêu cầu không khớp vẫn dùng model của chính mình.", + "compactionRouting.sourcesGridTitle": "Định tuyến lại yêu cầu nén có model nguồn khớp với:", "compactionRouting.currentModel": "Dùng model của cuộc trò chuyện", "compactionRouting.currentEffort": "Giữ mức suy luận của yêu cầu", "compactionRouting.effortHint": "Mức suy luận được áp dụng nếu điểm cuối nén hỗ trợ. Model phải tiếp nhận được toàn bộ cuộc trò chuyện.", "compactionRouting.dataNotice": "Yêu cầu nén thuộc phạm vi áp dụng sẽ gửi toàn bộ cuộc trò chuyện đến nhà cung cấp của model đã chọn để tóm tắt, ngay cả khi cuộc trò chuyện đang chạy ở nhà cung cấp khác.", "compactionRouting.autoNotice": "Nén tự động tự chạy, nên một cuộc trò chuyện dài có thể được gửi đến nhà cung cấp đó mà bạn không yêu cầu.", "compactionRouting.providerWarning": "Với thiết lập này, mỗi yêu cầu nén thuộc phạm vi áp dụng sẽ gửi toàn bộ nội dung cuộc trò chuyện đến {provider} để tóm tắt.", - "compactionRouting.comboWarning": "Với thiết lập này, mỗi yêu cầu nén thuộc phạm vi áp dụng sẽ gửi toàn bộ nội dung cuộc trò chuyện đến combo {combo} để tóm tắt. Combo thử lần lượt các đích ({providers}) và dùng đích trả lời đầu tiên, nên bất kỳ đích nào cũng có thể nhận cuộc trò chuyện.", + "compactionRouting.comboWarning": "Với thiết lập này, mỗi yêu cầu nén thuộc phạm vi áp dụng sẽ gửi toàn bộ nội dung cuộc trò chuyện đến combo {combo} để tóm tắt. Combo chọn một trong các đích ({providers}) theo chiến lược định tuyến của nó, nhưng sau lỗi có thể thử lại, nó có thể thử lại cùng một yêu cầu chứa toàn bộ cuộc trò chuyện ở một đích khác, nên một hoặc nhiều đích có thể nhận cuộc trò chuyện.", "compactionRouting.comboProvidersUnknown": "các nhà cung cấp đích đã cấu hình của nó", + "compactionRouting.providerWarningScoped": "Với thiết lập này, chỉ các yêu cầu nén có model nguồn khớp với {sources} mới gửi toàn bộ nội dung cuộc trò chuyện đến {provider} để tóm tắt; yêu cầu không khớp vẫn dùng model nguồn.", + "compactionRouting.comboWarningScoped": "Với thiết lập này, chỉ các yêu cầu nén có model nguồn khớp với {sources} mới gửi toàn bộ nội dung cuộc trò chuyện đến combo {combo} để tóm tắt; yêu cầu không khớp vẫn dùng model nguồn. Combo chọn một trong các đích ({providers}) theo chiến lược định tuyến của nó, nhưng sau lỗi có thể thử lại, nó có thể thử lại cùng một yêu cầu chứa toàn bộ cuộc trò chuyện ở một đích khác, nên một hoặc nhiều đích có thể nhận cuộc trò chuyện.", "compactionRouting.loadFailed": "Không thể tải cài đặt nén.", "compactionRouting.saved": "Đã lưu cài đặt nén.", "compactionRouting.saveFailed": "Không thể lưu. Thay đổi của bạn vẫn còn; hãy thử lại.", @@ -882,6 +897,17 @@ export const vi: Record = { "models.allowlistLabel": "Chỉ các mục đã chọn", "models.allowlistHint": "Chỉ các model được chọn mới gửi đến danh mục (để trống = tất cả). Hữu ích cho các provider cung cấp hàng nghìn model.", "models.selectedCount": "Đã chọn {n}", + "sub.forceTitle": "Buộc tất cả tác nhân con dùng cùng một mô hình", + "sub.forceModel": "Mô hình tác nhân con của Claude Code", + "sub.forceChoose": "Chọn một mô hình đã được công khai", + "sub.forceHelp": "Áp dụng cho tác nhân của plugin và tác nhân tích hợp (bao gồm Explore/Plan), ghi đè mô hình được chỉ định cho từng lần gọi. Các nhánh fork và kỹ năng có model: inherit vẫn dùng mô hình của cuộc trò chuyện chính. Mô hình chính và các mô hình phụ trợ Haiku/small-fast không bị ảnh hưởng.", + "sub.forceScope": "Mặc định tắt. Áp dụng từ lần khởi chạy ocx claude có định tuyến tiếp theo, không áp dụng khi chạy trực tiếp claude. Các biến môi trường được export trong shell có ưu tiên cao hơn; env trong settings.json có thể ghi đè chúng. Trạng thái chỉ kiểm tra CLI và cài đặt người dùng trên máy chủ, không kiểm tra shell khác hay cài đặt dự án.", + "sub.forceInvalid": "Mô hình đích đã cấu hình không khả dụng. Việc ghi đè mô hình khi khởi chạy sẽ bị bỏ qua; hãy chọn một mô hình đã được công khai hoặc tắt tính năng này.", + "sub.forceOld": "Phiên bản Claude Code cũ hơn 2.1.257: FORCE bị bỏ qua; chỉ áp dụng mô hình mặc định không bắt buộc của tác nhân con.", + "sub.forceUnknown": "Không xác định được phiên bản Claude Code. Tính năng buộc dùng mô hình yêu cầu phiên bản 2.1.257 trở lên.", + "sub.forceOverride": "Bị settings.json ghi đè: env trong tệp này chứa mô hình tác nhân con hoặc cài đặt FORCE. OpenCodex không sửa đổi tệp này.", + "sub.forceSettingsUnknown": "Không thể kiểm tra settings.json. Chưa xác định được liệu cài đặt trong tệp có ghi đè tùy chọn này hay không.", + "sub.forceSaved": "Đã lưu. Có hiệu lực ở lần khởi chạy ocx claude có định tuyến tiếp theo.", "sub.subtitle": "{cmd} của Codex chỉ quảng bá 5 model đầu tiên (theo mức độ ưu tiên) làm ghi đè (overrides). Chọn tối đa 5 model ở đây — native gpt hoặc routed — và opencodex sẽ đặt mức độ ưu tiên trong catalog của chúng để những model này dẫn đầu. Bất kỳ model nào khác vẫn có thể được gọi bằng đúng tên của nó; cài đặt này chỉ kiểm soát những gì được hiển thị.", "sub.featured": "Nổi bật (Featured)", "sub.advanced": "Nâng cao (Advanced)", @@ -1049,7 +1075,7 @@ export const vi: Record = { "logs.detail.ttft": "TTFT", "logs.detail.decodeTokPerSec": "Tốc độ decode (ước tính)", "logs.detail.reason.ttft_missing": "Không ghi nhận thời gian đến token đầu tiên (time-to-first-token) cho request này, nên không có cửa sổ decode nào để đo lường.", - "logs.detail.reason.decode_window_too_short": "Cửa sổ sau token đầu tiên nhỏ hơn một giây, quá ngắn để ước tính tốc độ decode.", + "logs.detail.reason.decode_window_too_short": "Cửa sổ đầu ra đo được nhỏ hơn một giây, quá ngắn để ước tính tốc độ decode.", "logs.detail.costTotal": "Tương đương giá niêm yết", "logs.detail.totalTokens": "Tổng số tokens", "logs.detail.matchedKey": "Khoá giá trùng khớp (Matched price key)", @@ -1305,9 +1331,12 @@ export const vi: Record = { "usage.col.reported": "Đã báo cáo", "usage.col.inputTokens": "Token đầu vào", "usage.col.outputTokens": "Token đầu ra", + "usage.col.tokPerSec": "Đầu ra tok/s", "usage.col.cacheHits": "Lượt truy cập cache", "usage.col.cacheWrites": "Lần ghi cache", "usage.col.cacheHitRate": "Tỷ lệ truy cập cache", + "usage.throughput.title": "Thông lượng đầu ra end-to-end: tổng token đầu ra chia cho tổng thời gian đồng hồ qua {samples} lần thử được đo. Bao gồm thời gian chờ trước khi giải mã nên thấp hơn tốc độ giải mã ổn định; các yêu cầu không đo được token và thời lượng bị loại.", + "usage.throughput.unmeasured": "Không có yêu cầu nào trong dòng này báo cả token đầu ra lẫn thời lượng, nên không có thông lượng để trung bình.", "usage.cacheHitRate.partial": "Giá trị trung bình được tính dựa trên {measured} trong tổng số {total} token đầu vào; các yêu cầu còn lại không cung cấp thông tin chi tiết về cache.", "usage.cacheHitRate.unmeasured": "Không có yêu cầu nào trong hàng này cung cấp thông tin chi tiết về cache, nên không thể tính tỷ lệ truy cập cache trung bình.", "usage.unavailable": "—", @@ -2004,6 +2033,16 @@ export const vi: Record = { "integrations.tab.cline": "Cline CLI", "integrations.tab.kilo": "Kilo", "integrations.tab.droid": "Factory Droid", + "integrations.droidReasoning.title": "Mức suy luận mặc định", + "integrations.droidReasoning.description": "Chỉ dùng mức mặc định khi yêu cầu không chỉ định mức suy luận. Tắt Factory Droid để xóa các mức mặc định đã lưu.", + "integrations.droidReasoning.noDefault": "Không có mặc định", + "integrations.droidReasoning.noEfforts": "Không có tùy chọn mức độ suy luận khả dụng", + "integrations.droidReasoning.noModels": "Không có mô hình nào được xuất.", + "integrations.droidReasoning.modelDefault": "Mức suy luận mặc định cho {model}", + "integrations.droidReasoning.unsupportedTitle": "Một số mức mặc định đã lưu không còn được hỗ trợ. Hãy xóa chúng, sau đó lưu / xem lại thay đổi để áp dụng.", + "integrations.droidReasoning.unsupportedEffort": "Mức không được hỗ trợ: {effort}", + "integrations.droidReasoning.clear": "Xóa mặc định", + "integrations.droidReasoning.saveReview": "Lưu / xem lại thay đổi", "integrations.aside.profilesTitle": "Cấu hình Aside", "integrations.aside.profilesHint": "Chọn profile nào sẽ nhận các model được chọn. Profile đang hoạt động của Aside sẽ được giữ nguyên.", "integrations.aside.all": "Đồng bộ tất cả profile", @@ -3517,6 +3556,15 @@ export const vi: Record = { "link.addChild": "Thêm máy con", "link.sheetTitle": "Thêm máy con", "link.candidates": "Máy chủ SSH", + "link.setup.relationship": "Child sẽ dùng các nhà cung cấp OpenCodex của Home này qua SSH.", + "link.setup.requirements": "Cần SSH bằng khóa từ Home đến Child, OpenCodex 2.66.0+ trên Child và macOS hoặc Linux trên cả hai máy.", + "link.setup.emptyHome": "Bí danh lấy từ ~/.ssh/config trên Home này. Thêm mục Host rồi quét lại, hoặc nhập bí danh hiện có bên dưới.", + "link.setup.emptyLocal": "Bí danh lấy từ ~/.ssh/config trên máy này. Thêm mục Host rồi quét lại, hoặc nhập bí danh hiện có bên dưới.", + "link.setup.guide": "Hướng dẫn thiết lập Remote Link", + "link.discoveryFailed": "Không thể tải máy chủ SSH", + "link.rescan": "Quét lại máy chủ", + "link.aliasRequired": "Chọn hoặc nhập bí danh để bật kiểm tra kết nối.", + "link.probeHelp": "Kiểm tra lý do ở trên. Nếu gặp vấn đề truy cập SSH, chạy lệnh này trong terminal trên máy này, thay bằng bí danh SSH rồi thử lại.", "link.noCandidates": "Không tìm thấy máy chủ SSH phù hợp.", "link.alias": "Bí danh máy chủ SSH", "link.aliasPlaceholder": "Nhập bí danh từ cấu hình SSH", @@ -3555,6 +3603,8 @@ export const vi: Record = { "remoteLink.error.join_restart_failed": "Liên kết đã sẵn sàng. Hãy khởi động lại OpenCodex trên máy tính này để hoàn tất kết nối với vai trò máy con.", "remoteLink.error.join_port_failed": "Không thể hoàn tất yêu cầu liên kết từ xa.", "remoteLink.error.join_port_mismatch": "OpenCodex không chạy trên cổng đã cấu hình nên không thể khởi động lại với vai trò máy con. Hãy khởi động lại OpenCodex trên cổng đã cấu hình rồi thử lại.", + "remoteLink.joinDenied.pairing_required": "Hãy ghép nối máy tính này trước để kết nối với vai trò máy con. Mở bảng điều khiển của máy tính này qua địa chỉ loopback rồi nhập mã ghép nối dùng một lần do người vận hành tạo.", + "remoteLink.joinDenied.unavailable": "Không thể kết nối với vai trò máy con trong phiên bảng điều khiển này. Hãy làm mới trạng thái và kiểm tra cài đặt ghép nối và runtime của máy tính này.", "remoteLink.error.join_connect_failed": "Không thể hoàn tất yêu cầu liên kết từ xa.", "link.noChildren": "Chưa có máy con nào được kết nối.", "remoteLink.status.connecting": "Đang kết nối", diff --git a/gui/src/i18n/vision-reasoning-labels.ts b/gui/src/i18n/vision-reasoning-labels.ts index a551f7dc4a6..600272c2510 100644 --- a/gui/src/i18n/vision-reasoning-labels.ts +++ b/gui/src/i18n/vision-reasoning-labels.ts @@ -13,6 +13,7 @@ const VISION_REASONING_LABELS = { ja: { low: "低", medium: "中", high: "高", xhigh: "非常に高い", max: "最大" }, tr: { low: "Düşük", medium: "Orta", high: "Yüksek", xhigh: "Çok yüksek", max: "Maksimum" }, vi: { low: "Thấp", medium: "Trung bình", high: "Cao", xhigh: "Rất cao", max: "Tối đa" }, + pt: { low: "Baixo", medium: "Médio", high: "Alto", xhigh: "Muito alto", max: "Máximo" }, } satisfies Record>; /** Localized display label for the wire-level vision reasoning value. */ diff --git a/gui/src/i18n/zh-TW.ts b/gui/src/i18n/zh-TW.ts index 46d84a91689..8159f43a3bb 100644 --- a/gui/src/i18n/zh-TW.ts +++ b/gui/src/i18n/zh-TW.ts @@ -141,6 +141,10 @@ export const zhTW: Record = { "theme.light": "淺色", "theme.dark": "深色", "theme.system": "跟隨系統", + "zoom.label": "縮放", + "zoom.out": "縮小", + "zoom.in": "放大", + "zoom.reset": "重設縮放為 100%", "lang.label": "語言", "errorBoundary.title": "頁面載入失敗", "errorBoundary.message": "此部分在渲染時發生錯誤。請重新載入後再試。", @@ -321,14 +325,25 @@ export const zhTW: Record = { "compactionRouting.triggersManual": "僅手動 /compact", "compactionRouting.triggersBoth": "手動與自動", "compactionRouting.triggersAuto": "僅自動", + "compactionRouting.sources": "來源範圍", + "compactionRouting.sourcesAll": "所有對話模型", + "compactionRouting.sourcesSelected": "僅選中的來源", + "compactionRouting.sourcesProviders": "整個提供商", + "compactionRouting.sourcesModels": "單個模型", + "compactionRouting.sourcesSaved": "已保存但不在目前目錄中的選取器", + "compactionRouting.sourcesNone": "至少選擇一個來源,或改回所有對話模型。", + "compactionRouting.sourcesHint": "僅當壓縮請求的來源模型符合所選模型或 provider/* 時才使用覆寫;其他請求仍使用自身模型壓縮。", + "compactionRouting.sourcesGridTitle": "改路由來源模型符合以下選擇器的壓縮請求:", "compactionRouting.currentModel": "使用對話模型", "compactionRouting.currentEffort": "保留請求的推理強度", "compactionRouting.effortHint": "推理設定僅在壓縮端點支援時生效。模型必須能容納完整對話。", "compactionRouting.dataNotice": "涵蓋的壓縮請求會將整個對話傳送給所選模型的供應商進行摘要,即使對話正在其他供應商上執行。", "compactionRouting.autoNotice": "自動壓縮會自行觸發,因此長對話可能在你沒有主動要求的情況下被傳送給該供應商。", "compactionRouting.providerWarning": "啟用此設定後,每個涵蓋的壓縮請求都會將完整對話內容傳送給 {provider} 進行摘要。", - "compactionRouting.comboWarning": "啟用此設定後,每個涵蓋的壓縮請求都會將完整對話內容傳送給組合 {combo} 以進行摘要。該組合會依序嘗試其目標({providers}),並使用第一個回應的目標,因此其中任一個都可能收到該對話。", + "compactionRouting.comboWarning": "啟用此設定後,每個涵蓋的壓縮請求都會將完整對話內容傳送給組合 {combo} 以進行摘要。該組合會依其路由策略選擇其中一個目標({providers}),但在發生可重試失敗後,可能於其他目標重試相同的完整對話請求,因此其中一個或多個目標都可能收到該對話。", "compactionRouting.comboProvidersUnknown": "其已設定的目標供應商", + "compactionRouting.providerWarningScoped": "啟用此設定後,僅當壓縮請求的來源模型符合 {sources} 時,完整對話內容才會傳送給 {provider} 進行摘要;其他請求仍使用自身模型壓縮。", + "compactionRouting.comboWarningScoped": "啟用此設定後,僅當壓縮請求的來源模型符合 {sources} 時,完整對話內容才會傳送給組合 {combo} 進行摘要;其他請求仍使用自身模型壓縮。該組合會依其路由策略選擇其中一個目標({providers}),但在發生可重試失敗後,可能於其他目標重試相同的完整對話請求,因此其中一個或多個目標都可能收到該對話。", "compactionRouting.loadFailed": "無法載入壓縮設定。", "compactionRouting.saved": "壓縮設定已儲存。", "compactionRouting.saveFailed": "儲存失敗。變更仍然保留,請重試。", @@ -731,6 +746,17 @@ export const zhTW: Record = { "models.allowlistLabel": "僅所選", "models.allowlistHint": "僅勾選的模型進入目錄(留空 = 全部)。適用於暴露成千上萬模型的供應商。", "models.selectedCount": "已選 {n} 個", + "sub.forceTitle": "強制所有子代理使用同一模型", + "sub.forceModel": "Claude Code 子代理模型", + "sub.forceChoose": "選擇已公開的模型", + "sub.forceHelp": "適用於外掛及內建代理(包括 Explore/Plan),並覆寫每次呼叫的模型。分支及 model: inherit 技能仍使用主對話模型。主模型及 Haiku/small-fast 輔助模型不受影響。", + "sub.forceScope": "預設關閉。從下次透過路由啟動 ocx claude 時生效,不影響直接執行 claude。Shell 環境變數優先;settings.json 的 env 可覆寫它們。狀態僅檢查伺服器的 CLI 與使用者設定,不涵蓋其他 Shell 或專案設定。", + "sub.forceInvalid": "已設定的目標無法使用,啟動時會略過覆寫。請選擇已公開的模型或關閉此功能。", + "sub.forceOld": "Claude Code 版本低於 2.1.257:FORCE 會被忽略,僅套用非強制的子代理預設模型。", + "sub.forceUnknown": "無法確認 Claude Code 版本。強制功能需要 2.1.257 或更新版本。", + "sub.forceOverride": "已被 settings.json 覆寫:其 env 含有子代理模型或 FORCE 設定。OpenCodex 不會修改該檔案。", + "sub.forceSettingsUnknown": "無法檢查 settings.json,設定覆寫狀態未知。", + "sub.forceSaved": "已儲存。從下次透過路由啟動 ocx claude 時生效。", "sub.subtitle": "Codex 的 {cmd} 僅將優先順序最高的前 5 個模型作為覆蓋項公開。在此最多選擇 5 個 — 原生 gpt 或已路由模型 — opencodex 會設定它們的目錄優先順序,使其正好排在前面。其他模型仍可按確切名稱呼叫;此設定僅控制顯示項。", "sub.featured": "精選", "sub.advanced": "進階", @@ -829,7 +855,7 @@ export const zhTW: Record = { "logs.detail.ttft": "TTFT", "logs.detail.decodeTokPerSec": "解碼速率(估算)", "logs.detail.reason.ttft_missing": "此請求沒有記錄第一個 token 的時間,因此沒有可測量的解碼區間。", - "logs.detail.reason.decode_window_too_short": "第一個 token 之後的區間不到一秒,太短,無法估算解碼速率。", + "logs.detail.reason.decode_window_too_short": "測得的輸出區間不到一秒,太短,無法估算解碼速率。", "logs.detail.costTotal": "標價折算", "logs.detail.totalTokens": "Token 總數", "logs.detail.matchedKey": "符合的 jawcode 鍵", @@ -1023,9 +1049,12 @@ export const zhTW: Record = { "usage.col.reported": "已上報", "usage.col.inputTokens": "輸入 Token", "usage.col.outputTokens": "輸出 Token", + "usage.col.tokPerSec": "輸出 tok/s", "usage.col.cacheHits": "快取命中", "usage.col.cacheWrites": "快取寫入", "usage.col.cacheHitRate": "命中率", + "usage.throughput.title": "端到端輸出吞吐:輸出 token 總和 ÷ 牆鐘時長總和,樣本為 {samples} 個同時上報了 token 與時長的嘗試。包含解碼前的等待時間,因此低於穩態解碼速度;未上報 token 或時長的請求不計入。", + "usage.throughput.unmeasured": "該行沒有請求同時上報輸出 token 與時長,無法計算吞吐。", "usage.cacheHitRate.partial": "全部 {total} 個輸入 Token 中,有 {measured} 個提供了快取明細,命中率取這些資料的平均值;其餘請求未回報快取明細。", "usage.cacheHitRate.unmeasured": "此資料列的所有請求都未回報快取明細,因此無法計算平均命中率。", "usage.unavailable": "—", @@ -2896,6 +2925,16 @@ export const zhTW: Record = { "integrations.tab.cline": "Cline CLI", "integrations.tab.kilo": "Kilo", "integrations.tab.droid": "Factory Droid", + "integrations.droidReasoning.title": "預設推理強度", + "integrations.droidReasoning.description": "只有在要求未指定推理強度時才會使用預設值。停用 Factory Droid 會移除這些已儲存的預設值。", + "integrations.droidReasoning.noDefault": "無預設值", + "integrations.droidReasoning.noEfforts": "沒有可用的推理強度選項", + "integrations.droidReasoning.noModels": "沒有可匯出的模型。", + "integrations.droidReasoning.modelDefault": "{model} 的預設推理強度", + "integrations.droidReasoning.unsupportedTitle": "部分已儲存的預設值已不再支援。清除後,請儲存 / 檢查變更以套用。", + "integrations.droidReasoning.unsupportedEffort": "不支援的強度:{effort}", + "integrations.droidReasoning.clear": "清除預設值", + "integrations.droidReasoning.saveReview": "儲存 / 檢查變更", "integrations.aside.profilesTitle": "Aside 設定檔", "integrations.aside.profilesHint": "選擇要接收所選模型的設定檔。Aside 目前使用的設定檔不會改變。", "integrations.aside.all": "同步所有設定檔", @@ -3545,6 +3584,15 @@ export const zhTW: Record = { "link.addChild": "新增子裝置", "link.sheetTitle": "新增子裝置", "link.candidates": "SSH 主機", + "link.setup.relationship": "Child 將透過 SSH 使用此 Home 的 OpenCodex 供應商。", + "link.setup.requirements": "需要從 Home 到 Child 的 SSH 金鑰驗證、Child 上的 OpenCodex 2.66.0+,以及兩台電腦上的 macOS 或 Linux。", + "link.setup.emptyHome": "別名來自此 Home 的 ~/.ssh/config。新增 Host 項目後重新掃描,或在下方輸入現有別名。", + "link.setup.emptyLocal": "別名來自此電腦的 ~/.ssh/config。新增 Host 項目後重新掃描,或在下方輸入現有別名。", + "link.setup.guide": "Remote Link 設定指南", + "link.discoveryFailed": "無法載入 SSH 主機", + "link.rescan": "重新掃描主機", + "link.aliasRequired": "選取或輸入別名以啟用連線測試。", + "link.probeHelp": "請檢查上述原因。如果 SSH 存取有問題,請在此電腦的終端機中執行以下命令,將 替換為 SSH 別名,然後重試。", "link.noCandidates": "找不到 SSH 主機候選項。", "link.alias": "SSH 主機別名", "link.aliasPlaceholder": "輸入 SSH 設定中的別名", @@ -3583,6 +3631,8 @@ export const zhTW: Record = { "remoteLink.error.join_restart_failed": "連線已準備就緒。請在此電腦上重新啟動 OpenCodex,以完成作為子裝置的連線。", "remoteLink.error.join_port_failed": "無法完成遠端連線要求。", "remoteLink.error.join_port_mismatch": "OpenCodex 未在其設定的連接埠上執行,因此無法以子裝置身分重新啟動。請在設定的連接埠上重新啟動 OpenCodex,然後再試一次。", + "remoteLink.joinDenied.pairing_required": "請先配對此電腦,再以子裝置身分連線。透過此電腦的 loopback 位址開啟儀表板,並輸入管理員建立的一次性配對碼。", + "remoteLink.joinDenied.unavailable": "此儀表板工作階段無法以子裝置身分連線。請重新整理狀態,並檢查此電腦的配對與執行環境設定。", "remoteLink.error.join_connect_failed": "無法完成遠端連線要求。", "link.noChildren": "沒有已連線的子裝置。", "remoteLink.status.connecting": "連線中", diff --git a/gui/src/i18n/zh.ts b/gui/src/i18n/zh.ts index 0ffc79db81f..31764564d3c 100644 --- a/gui/src/i18n/zh.ts +++ b/gui/src/i18n/zh.ts @@ -238,6 +238,10 @@ export const zh: Record = { "theme.light": "浅色", "theme.dark": "深色", "theme.system": "跟随系统", + "zoom.label": "缩放", + "zoom.out": "缩小", + "zoom.in": "放大", + "zoom.reset": "将缩放重置为 100%", "lang.label": "语言", "lang.nativeName": "中文", "provider.name.commandCodeAuth": "Command Code - Auth", @@ -439,14 +443,25 @@ export const zh: Record = { "compactionRouting.triggersManual": "仅手动 /compact", "compactionRouting.triggersBoth": "手动和自动", "compactionRouting.triggersAuto": "仅自动", + "compactionRouting.sources": "来源范围", + "compactionRouting.sourcesAll": "所有对话模型", + "compactionRouting.sourcesSelected": "仅选中的来源", + "compactionRouting.sourcesProviders": "整个提供商", + "compactionRouting.sourcesModels": "单个模型", + "compactionRouting.sourcesSaved": "已保存但不在当前目录中的选择器", + "compactionRouting.sourcesNone": "至少选择一个来源,或改回所有对话模型。", + "compactionRouting.sourcesHint": "仅当压缩请求的来源模型匹配所选模型或 provider/* 时才使用覆盖;其他请求仍使用自身模型压缩。", + "compactionRouting.sourcesGridTitle": "改路由来源模型匹配以下选择器的压缩请求:", "compactionRouting.currentModel": "使用对话模型", "compactionRouting.currentEffort": "保留请求的推理强度", "compactionRouting.effortHint": "推理设置仅在压缩端点支持时生效。模型必须能容纳完整对话。", "compactionRouting.dataNotice": "被覆盖的压缩请求会将整个对话发送给所选模型的提供商进行摘要,即使对话正在其他提供商上运行。", "compactionRouting.autoNotice": "自动压缩会自行触发,因此长对话可能在你没有主动要求的情况下被发送给该提供商。", "compactionRouting.providerWarning": "启用此设置后,每个被覆盖的压缩请求都会将完整对话内容发送给 {provider} 进行摘要。", - "compactionRouting.comboWarning": "启用此设置后,每个被覆盖的压缩请求都会将完整对话内容发送给组合 {combo} 以进行摘要。该组合会按顺序尝试其目标({providers}),并使用第一个响应的目标,因此其中任意一个都可能收到该对话。", + "compactionRouting.comboWarning": "启用此设置后,每个被覆盖的压缩请求都会将完整对话内容发送给组合 {combo} 以进行摘要。该组合会按其路由策略选择其中一个目标({providers}),但在发生可重试失败后,可能在其他目标重试相同的完整对话请求,因此其中一个或多个目标都可能收到该对话。", "compactionRouting.comboProvidersUnknown": "其已配置的目标提供商", + "compactionRouting.providerWarningScoped": "启用此设置后,仅当压缩请求的来源模型匹配 {sources} 时,完整对话内容才会发送给 {provider} 进行摘要;其他请求仍使用自身模型压缩。", + "compactionRouting.comboWarningScoped": "启用此设置后,仅当压缩请求的来源模型匹配 {sources} 时,完整对话内容才会发送给组合 {combo} 进行摘要;其他请求仍使用自身模型压缩。该组合会按其路由策略选择其中一个目标({providers}),但在发生可重试失败后,可能在其他目标重试相同的完整对话请求,因此其中一个或多个目标都可能收到该对话。", "compactionRouting.loadFailed": "无法加载压缩设置。", "compactionRouting.saved": "压缩设置已保存。", "compactionRouting.saveFailed": "保存失败。更改仍然保留,请重试。", @@ -877,6 +892,17 @@ export const zh: Record = { "models.selectedCount": "已选 {n} 个", // subagents + "sub.forceTitle": "强制所有子代理使用同一模型", + "sub.forceModel": "Claude Code 子代理模型", + "sub.forceChoose": "选择已公开的模型", + "sub.forceHelp": "适用于插件和内置代理(包括 Explore/Plan),并覆盖每次调用指定的模型。分支和设置了 model: inherit 的技能仍使用主对话模型。主模型和 Haiku/small-fast 辅助模型不受影响。", + "sub.forceScope": "默认关闭。从下次通过路由启动 ocx claude 时生效,不适用于直接运行 claude。Shell 导出的环境变量优先;settings.json 的 env 可覆盖这些变量。状态仅检查服务器上的 CLI 和用户设置,不涵盖其他 Shell 或项目设置。", + "sub.forceInvalid": "配置的目标不可用,启动时将跳过模型覆盖。请选择已公开的模型或关闭此功能。", + "sub.forceOld": "Claude Code 版本低于 2.1.257:FORCE 会被忽略,仅应用非强制的子代理默认模型。", + "sub.forceUnknown": "无法确定 Claude Code 版本。强制功能需要 2.1.257 或更新版本。", + "sub.forceOverride": "已被 settings.json 覆盖:其 env 中包含子代理模型或 FORCE 设置。OpenCodex 不会修改该文件。", + "sub.forceSettingsUnknown": "无法检查 settings.json,无法确定设置是否覆盖了此选项。", + "sub.forceSaved": "已保存。从下次通过路由启动 ocx claude 时生效。", "sub.subtitle": "Codex 的 {cmd} 仅将优先级最高的前 5 个模型作为覆盖项公开。在此最多选择 5 个 — 原生 gpt 或已路由模型 — opencodex 会设置它们的目录优先级,使其正好排在前面。其他模型仍可按确切名称调用;此设置仅控制显示项。", "sub.featured": "精选", "sub.advanced": "高级", @@ -1023,7 +1049,7 @@ export const zh: Record = { "logs.detail.ttft": "TTFT", "logs.detail.decodeTokPerSec": "解码速率(估算)", "logs.detail.reason.ttft_missing": "该请求没有记录首个 token 的时间,因此没有可测量的解码区间。", - "logs.detail.reason.decode_window_too_short": "首个 token 之后的区间不足一秒,太短,无法估算解码速率。", + "logs.detail.reason.decode_window_too_short": "测得的输出区间不足一秒,太短,无法估算解码速率。", "logs.detail.costTotal": "标价折算", "logs.detail.totalTokens": "Token 总数", "logs.detail.matchedKey": "匹配的价格键", @@ -1282,9 +1308,12 @@ export const zh: Record = { "usage.col.reported": "已上报", "usage.col.inputTokens": "输入 Token", "usage.col.outputTokens": "输出 Token", + "usage.col.tokPerSec": "输出 tok/s", "usage.col.cacheHits": "缓存命中", "usage.col.cacheWrites": "缓存写入", "usage.col.cacheHitRate": "命中率", + "usage.throughput.title": "端到端输出吞吐:输出 token 总和 ÷ 墙钟时长总和,样本为 {samples} 个同时上报了 token 与时长的尝试。包含解码前的等待时间,因此低于稳态解码速度;未上报 token 或时长的请求不计入。", + "usage.throughput.unmeasured": "该行没有请求同时上报输出 token 与时长,无法计算吞吐。", "usage.cacheHitRate.partial": "全部 {total} 个输入 Token 中,有 {measured} 个提供了缓存明细,命中率取这些数据的平均值;其余请求未报告缓存明细。", "usage.cacheHitRate.unmeasured": "此行没有任何请求报告缓存明细,因此无法计算平均命中率。", "usage.unavailable": "—", @@ -1500,6 +1529,16 @@ export const zh: Record = { "integrations.tab.cline": "Cline CLI", "integrations.tab.kilo": "Kilo", "integrations.tab.droid": "Factory Droid", + "integrations.droidReasoning.title": "默认推理强度", + "integrations.droidReasoning.description": "仅当请求未指定推理强度时才使用默认值。停用 Factory Droid 会删除这些已保存的默认值。", + "integrations.droidReasoning.noDefault": "无默认值", + "integrations.droidReasoning.noEfforts": "没有可用的推理强度选项", + "integrations.droidReasoning.noModels": "没有可导出的模型。", + "integrations.droidReasoning.modelDefault": "{model} 的默认推理强度", + "integrations.droidReasoning.unsupportedTitle": "部分已保存的默认值已不再受支持。清除后,请保存 / 检查更改以应用。", + "integrations.droidReasoning.unsupportedEffort": "不支持的强度:{effort}", + "integrations.droidReasoning.clear": "清除默认值", + "integrations.droidReasoning.saveReview": "保存 / 检查更改", "integrations.aside.profilesTitle": "Aside 配置文件", "integrations.aside.profilesHint": "选择要接收所选模型的配置文件。Aside 当前使用的配置文件不会改变。", "integrations.aside.all": "同步所有配置文件", @@ -3580,6 +3619,15 @@ export const zh: Record = { "link.addChild": "添加子设备", "link.sheetTitle": "添加子设备", "link.candidates": "SSH 主机", + "link.setup.relationship": "子机将通过 SSH 使用此主机的 OpenCodex 提供商。", + "link.setup.requirements": "需要从主机到子机的 SSH 密钥认证、子机上的 OpenCodex 2.66.0+,以及两台电脑上的 macOS 或 Linux。", + "link.setup.emptyHome": "别名来自此主机的 ~/.ssh/config。添加 Host 条目后重新扫描,或在下方输入已有别名。", + "link.setup.emptyLocal": "别名来自此电脑的 ~/.ssh/config。添加 Host 条目后重新扫描,或在下方输入已有别名。", + "link.setup.guide": "Remote Link 设置指南", + "link.discoveryFailed": "无法加载 SSH 主机", + "link.rescan": "重新扫描主机", + "link.aliasRequired": "选择或输入别名以启用连接测试。", + "link.probeHelp": "请检查上述原因。如果 SSH 访问有问题,请在此电脑的终端中运行以下命令,将 替换为 SSH 别名,然后重试。", "link.noCandidates": "未找到 SSH 主机候选项。", "link.alias": "SSH 主机别名", "link.aliasPlaceholder": "输入 SSH 配置中的别名", @@ -3618,6 +3666,8 @@ export const zh: Record = { "remoteLink.error.join_restart_failed": "连接已准备就绪。请在此电脑上重启 OpenCodex,以完成作为子设备的连接。", "remoteLink.error.join_port_failed": "无法完成远程连接请求。", "remoteLink.error.join_port_mismatch": "OpenCodex 未在其配置的端口上运行,因此无法以子设备身份重启。请在配置的端口上重启 OpenCodex,然后重试。", + "remoteLink.joinDenied.pairing_required": "请先配对此电脑,再以子设备身份连接。通过此电脑的 loopback 地址打开仪表盘,并输入管理员创建的一次性配对码。", + "remoteLink.joinDenied.unavailable": "此仪表盘会话无法以子设备身份连接。请刷新状态,并检查此电脑的配对与运行时设置。", "remoteLink.error.join_connect_failed": "无法完成远程连接请求。", "link.noChildren": "没有已连接的子设备。", "remoteLink.status.connecting": "连接中", diff --git a/gui/src/icons.tsx b/gui/src/icons.tsx index a30ba9f18d9..cf462aec3c4 100644 --- a/gui/src/icons.tsx +++ b/gui/src/icons.tsx @@ -21,6 +21,7 @@ export const IconHardDrive = (p: P) => ( (); export const IconX = (p: P) => (); export const IconPlus = (p: P) => (); +export const IconMinus = (p: P) => (); export const IconRefresh = (p: P) => (); export const IconPause = (p: P) => (); export const IconPlay = (p: P) => (); diff --git a/gui/src/lib/desktop-zoom.ts b/gui/src/lib/desktop-zoom.ts new file mode 100644 index 00000000000..25ce0d3a87f --- /dev/null +++ b/gui/src/lib/desktop-zoom.ts @@ -0,0 +1,121 @@ +/** + * Page zoom for the desktop shell, owned by the dashboard. + * + * Tauri's own zoom-hotkey polyfill keeps the level in a script variable that starts at 1 on + * every page load: a restart forgot the level, and so did the hop from the bootstrap page to + * the dashboard, after which the first shortcut jumped from the real level to 120%. The + * dashboard therefore keeps the level itself, remembers it, and drives the webview through the + * one command `desktop/src-tauri/capabilities/dashboard-zoom.json` already grants to this + * origin. Outside the shell the global is absent and every call is a no-op. + */ +import type { HostOs } from "./desktop-shell"; + +export const ZOOM_STORAGE_KEY = "ocx-desktop-zoom"; +export const ZOOM_MIN = 0.5; +export const ZOOM_MAX = 3; +export const ZOOM_STEP = 0.1; +export const ZOOM_DEFAULT = 1; + +export type ZoomAction = "in" | "out" | "reset"; + +declare global { + interface Window { + __TAURI_INTERNALS__?: { + invoke?: (command: string, args?: Record) => Promise; + }; + } +} + +/** + * Whether the dashboard drives zoom on this host. Windows keeps WebView2's native zoom, which + * the shell leaves enabled there, so a second owner would only disagree with it. + */ +export function zoomManagedOn(os: HostOs): boolean { + return os === "macos" || os === "linux"; +} + +/** Whole percent steps keep repeated presses from drifting into 1.2000000000000002. */ +export function clampZoom(value: number): number { + if (!Number.isFinite(value)) return ZOOM_DEFAULT; + const bounded = Math.min(Math.max(value, ZOOM_MIN), ZOOM_MAX); + return Math.round(bounded * 100) / 100; +} + +export function stepZoom(current: number, action: ZoomAction): number { + if (action === "reset") return ZOOM_DEFAULT; + return clampZoom(current + (action === "in" ? ZOOM_STEP : -ZOOM_STEP)); +} + +export function zoomPercent(zoom: number): number { + return Math.round(zoom * 100); +} + +export function readSavedZoom( + storage: Pick | undefined = typeof localStorage === "undefined" ? undefined : localStorage, +): number { + try { + const raw = storage?.getItem(ZOOM_STORAGE_KEY); + if (raw === null || raw === undefined) return ZOOM_DEFAULT; + return clampZoom(Number(raw)); + } catch { + return ZOOM_DEFAULT; + } +} + +export function writeSavedZoom( + zoom: number, + storage: Pick | undefined = typeof localStorage === "undefined" ? undefined : localStorage, +): void { + try { + storage?.setItem(ZOOM_STORAGE_KEY, String(zoom)); + } catch { + // A storageless context still zooms for the session. + } +} + +/** + * The key combinations the shell always answered to: Cmd on macOS, Ctrl elsewhere, with plus, + * minus and zero. `=` is accepted because plus shares its key on most layouts. + */ +export function zoomKeyAction( + event: Pick, + os: HostOs, +): ZoomAction | null { + const modifier = os === "macos" ? event.metaKey : event.ctrlKey; + if (!modifier) return null; + if (event.key === "-" || event.key === "_") return "out"; + if (event.key === "=" || event.key === "+") return "in"; + if (event.key === "0") return "reset"; + return null; +} + +/** Distance a Ctrl + wheel gesture must add up to before it steps once. */ +export const WHEEL_STEP_PX = 50; + +/** + * Ctrl + wheel, as before: up zooms in. A mouse notch is one step, while a touchpad pinch + * arrives as many tiny deltas, so the distance accumulates instead of stepping on every event. + */ +export function accumulateWheel( + accumulated: number, + deltaY: number, +): { accumulated: number; action: ZoomAction | null } { + if (!Number.isFinite(deltaY) || deltaY === 0) return { accumulated, action: null }; + // A change of direction starts a new gesture rather than cancelling the last one. + const carried = Math.sign(accumulated) === Math.sign(deltaY) ? accumulated : 0; + const total = carried + deltaY; + if (Math.abs(total) < WHEEL_STEP_PX) return { accumulated: total, action: null }; + return { accumulated: 0, action: total < 0 ? "in" : "out" }; +} + +/** Asks the shell to set the webview zoom. Resolves false when there is no shell to ask. */ +export async function applyWebviewZoom(zoom: number): Promise { + const invoke = window.__TAURI_INTERNALS__?.invoke ?? window.__TAURI__?.core?.invoke; + if (!invoke) return false; + try { + await invoke("plugin:webview|set_webview_zoom", { value: zoom }); + return true; + } catch { + return false; + } +} diff --git a/gui/src/main.tsx b/gui/src/main.tsx index 49db60f99e5..332f0bb3869 100644 --- a/gui/src/main.tsx +++ b/gui/src/main.tsx @@ -19,6 +19,7 @@ import "./styles/lazycodex-role-models.css"; import "./styles/claude-desktop-picker.css"; import "./styles/anthropic-reset-grants.css"; import "./styles/star-onboarding.css"; +import "./styles/sidebar-zoom.css"; import "./styles/protocol-evidence.css"; import "./pages/tray.css"; diff --git a/gui/src/model-visibility.ts b/gui/src/model-visibility.ts index df73ff51862..2cece6b7115 100644 --- a/gui/src/model-visibility.ts +++ b/gui/src/model-visibility.ts @@ -82,11 +82,13 @@ export async function putModelVisibility( targets: ModelVisibilityTarget[], enabled: boolean, fetchImpl: typeof fetch = fetch, + signal?: AbortSignal, ): Promise { return fetchImpl(`${apiBase}/api/model-visibility`, { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ scope, provider, targets, enabled }), + ...(signal ? { signal } : {}), }); } diff --git a/gui/src/pages/Models.tsx b/gui/src/pages/Models.tsx index cfd1d89f057..6c0a446fb81 100644 --- a/gui/src/pages/Models.tsx +++ b/gui/src/pages/Models.tsx @@ -26,6 +26,7 @@ import { type ModelPickerOrderMode, type PickerOrderSettings, type PickerOrderSaved, type ModelPickerUsage, } from "../model-picker-order"; import { startVisibilityPoll } from "../visibility-poll"; +import { useModelVisibility } from "../use-model-visibility"; import { useDataSurface } from "../data-surface"; import { DataSurfaceSkeleton } from "../components/data-surface"; import ErrorBoundary from "../components/ErrorBoundary"; @@ -48,8 +49,7 @@ import { } from "../models-groups"; import { fetchSelectedModels, - modelVisible, - putModelVisibility, + modelVisible as savedModelVisible, clientCatalogRefreshFailures, type ClientCatalogRefreshFailure, shouldApplyLoadGeneration, @@ -340,6 +340,18 @@ export default function Models({ apiBase, restartEpoch = 0, connected = false, c const catalogMutationRef = useRef(false); const loadGenerationRef = useRef(0); const loadPendingRef = useRef(false); + const visibility = useModelVisibility(apiBase, { + onQueued: () => { ++loadGenerationRef.current; setStatus(""); }, + onBusy: value => { ++loadGenerationRef.current; loadPendingRef.current = false; catalogMutationRef.current = value; busyRef.current = value; setBusy(value); }, + onResponse: body => { + const failures = clientCatalogRefreshFailures(body); + if (failures !== undefined) setIntegrationFailures(failures); + }, + refresh: signal => load(true, signal), + onSettled: error => { setOk(!error); setStatus(t(error ?? "models.applied")); }, + }); + const modelVisible = (selected: ProviderModelMap, provider: string, id: string, native: boolean, blocked: boolean) => + visibility.visible(provider, id, native, savedModelVisible(selected, provider, id, native, blocked)); // multi_agent_v2 / ultra gate. null = endpoint unavailable (older proxy build) -> section hidden. const [v2, setV2] = useState(null); // #2465: per-provider model-preset state. Keyed by provider so one card's busy state cannot @@ -497,6 +509,7 @@ export default function Models({ apiBase, restartEpoch = 0, connected = false, c }, [apiBase]); const fetchCatalog = useCallback(async (signal: AbortSignal): Promise => { + const generation = loadGenerationRef.current; const [modelsRes, capsRes, providersRes, selectionData] = await Promise.all([ // Every request carries the resource signal, so leaving the catalog tab cancels // the work rather than only discarding its result. @@ -528,7 +541,7 @@ export default function Models({ apiBase, restartEpoch = 0, connected = false, c contextCapValues: capsData.values ?? capsData.caps ?? {}, contextCapValue: nextCapValue, } satisfies CachedModelsPage; - writeSessionListCache(cacheKey, next); + if (generation === loadGenerationRef.current) writeSessionListCache(cacheKey, next); return next; }, [apiBase, cacheKey]); @@ -552,11 +565,12 @@ export default function Models({ apiBase, restartEpoch = 0, connected = false, c cacheKey, [apiBase], async (signal) => { + const generation = loadGenerationRef.current; const next = await fetchCatalog(signal); // A manual mutation refresh may have invalidated this request while its JSON was decoding. // Do not let the aborted catalog repaint controls after the newer result is applied. if (signal.aborted) throw new Error("models request aborted"); - applyCatalog(next); + if (!catalogMutationRef.current && generation === loadGenerationRef.current) applyCatalog(next); return next; }, // Gated on the catalog tab: a 10-second poll that keeps running while the user @@ -888,7 +902,7 @@ export default function Models({ apiBase, restartEpoch = 0, connected = false, c model.native === true, disabled.has(model.namespaced), )).length; - }, [disabled, models, selectedModels]); + }, [disabled, models, selectedModels, visibility.overrides]); /* * Quiet per-tab counts. A count is omitted, never zeroed, while it is unknown: the @@ -910,35 +924,8 @@ export default function Models({ apiBase, restartEpoch = 0, connected = false, c targets: ModelVisibilityTarget[], enabled: boolean, ) => { - if (catalogMutationRef.current) return; - catalogMutationRef.current = true; - ++loadGenerationRef.current; - setBusy(true); - busyRef.current = true; - setStatus(""); - let errorKey: "models.saveFailed" | "models.networkError" | null = null; - try { - const response = await putModelVisibility(apiBase, scope, provider, targets, enabled); - if (!response.ok) errorKey = "models.saveFailed"; - else { - const failures = clientCatalogRefreshFailures(await response.json()); - if (failures !== undefined) setIntegrationFailures(failures); - } - } catch { - errorKey = "models.networkError"; - } finally { - const refreshed = await load(true); - if (errorKey) { - setOk(false); - setStatus(t(errorKey)); - } else if (refreshed) { - setOk(true); - setStatus(t("models.applied")); - } - setBusy(false); - busyRef.current = false; - catalogMutationRef.current = false; - } + if (busyRef.current && !visibility.isRunning()) return; + visibility.enqueue(scope, provider, targets, enabled); }; const toggleProviderCap = async (provider: string) => { @@ -1589,8 +1576,8 @@ export default function Models({ apiBase, restartEpoch = 0, connected = false, c ); })()} - - + +
{/* The label names the FUNCTION. It used to be `models.capValue` - "기본 128k" - which is a value masquerading as a name: even a @@ -1730,7 +1717,7 @@ export default function Models({ apiBase, restartEpoch = 0, connected = false, c }} >
- void applyVisibility("models", provider, [{ id: m.id, native: m.native === true }], off)} disabled={busy || m.initialSelectionPending} label={m.native ? m.id : m.namespaced} /> + void applyVisibility("models", provider, [{ id: m.id, native: m.native === true }], off)} disabled={(busy && !visibility.pending) || m.initialSelectionPending} label={m.native ? m.id : m.namespaced} /> {m.initialSelectionPending && {t("models.initialSelectionPending")}} {/* #1711: listed and selectable, but every usable target is out of credit. Not a visibility change and not the operator's disable flag — the row is @@ -2568,7 +2555,7 @@ export default function Models({ apiBase, restartEpoch = 0, connected = false, c pickerMode: modelPickerOrderMode(pickerSettings?.pickerAvailable ?? [], pickerSettings?.pickerOrder ?? [], pickerSettings?.pickerOrderMode) })}> {controlsBlock} -
+