diff --git a/docs-site/src/content/docs/fr/guides/combos.md b/docs-site/src/content/docs/fr/guides/combos.md index 4cb051fc727..42469f4fa28 100644 --- a/docs-site/src/content/docs/fr/guides/combos.md +++ b/docs-site/src/content/docs/fr/guides/combos.md @@ -277,7 +277,7 @@ flux de travail des tâches. ### Tableau de bord Ouvrez le tableau de bord local et choisissez **Modèles → Combos**. L'espace de travail crée, modifie, renomme et supprime -combos, et son sélecteur de cible exclut les modèles désactivés et les combos imbriqués. +les combos. Les sélecteurs de modèles incluent les modèles masqués : la visibilité ne concerne que les listes externes, pas la sélection interne des combos. Sélectionner un modèle masqué ne le rend pas visible. Le sélecteur de cible exclut toujours les fournisseurs désactivés, les combos imbriqués et les fournisseurs JEV réservés au service de décision. Chaque cible affiche aussi un badge de quota en direct : **Disponible**, **Quota épuisé** ou **Quota inconnu**. L’éditeur bloque Enregistrer et Créer pour une raison de quota uniquement lorsque chaque cible utilisable dispose d’une confirmation serveur encore valide indiquant que la limite d’inférence liée à ses identifiants configurés est épuisée. Les quotas de compte, de modèle, de recherche et de MCP fournis uniquement à titre d’affichage, ainsi que les informations de routage absentes ou expirées, ne déclenchent pas ce blocage. Le blocage expire à la réinitialisation applicable ou à l’expiration de la validité des données et fait l’objet d’une nouvelle vérification lorsque la page devient active ou visible ; Actualiser recharge à la fois les données des combos et les quotas. diff --git a/docs-site/src/content/docs/guides/combos.md b/docs-site/src/content/docs/guides/combos.md index 60e3db08e6b..10b96325810 100644 --- a/docs-site/src/content/docs/guides/combos.md +++ b/docs-site/src/content/docs/guides/combos.md @@ -739,8 +739,9 @@ task workflow. ### Dashboard Open the local dashboard and choose **Models → Combos**. The workspace creates, edits, renames, and removes -combos, and its target picker excludes disabled models, nested combos, and the credential-only JEV -provider. **Create JEV Auto** opens the same Combo editor with an editable decision target template; +combos. Its model selectors include hidden models: catalog visibility only controls external model +lists, not internal Combo selection, and selecting a hidden model does not make it visible externally. +The target picker still excludes disabled providers, nested combos, and the credential-only JEV provider. **Create JEV Auto** opens the same Combo editor with an editable decision target template; an existing `jev-auto` id or alias is reported instead of creating a duplicate. A JEV Combo also shows **Decision service** (TypeSafe JEV or a configured `jev-decision` provider) and **Decision timeout (ms)**, and the Combos overview lists each JEV Combo's decision service, endpoint, and timeout. diff --git a/docs-site/src/content/docs/ja/guides/combos.md b/docs-site/src/content/docs/ja/guides/combos.md index 1a03b3e6a07..2a9991087e2 100644 --- a/docs-site/src/content/docs/ja/guides/combos.md +++ b/docs-site/src/content/docs/ja/guides/combos.md @@ -184,7 +184,7 @@ v1/base/v2 モードと完全な暗号化タスクのワークフローについ ### ダッシュボード -ローカル ダッシュボードを開き、**Models → コンボ**を選択します。ワークスペースはコンボを作成、編集、名前変更、削除し、そのターゲット ピッカーは無効なモデルとネストされたコンボを除外します。 +ローカル ダッシュボードを開き、**Models → コンボ**を選択します。ワークスペースではコンボの作成、編集、名前変更、削除ができます。モデル選択には非表示モデルも含まれます。表示設定は外部のモデル一覧にのみ適用され、コンボ内部の選択には影響しません。非表示モデルを選択しても外部には表示されません。ターゲット選択では、無効なプロバイダー、ネストされたコンボ、決定サービス専用の JEV プロバイダーは引き続き除外されます。 各ターゲットには **利用可能**、**クォータを使い切りました**、**クォータ不明** のライブバッジも表示されます。 エディターがクォータを理由に保存と作成をブロックするのは、使用可能なすべてのターゲットについて、設定された認証情報の推論上限に達したことを示す、サーバーによる確認が現在も有効な場合だけです。表示専用のアカウント・モデル・検索・MCP クォータや、ルーティングの根拠情報の欠落・期限切れによって、このブロックが発生することはありません。ブロックは該当するリセット時刻またはデータの有効期限に解除され、ページがアクティブになるか表示状態になると再確認されます。「更新」はコンボデータとクォータの両方を再読み込みします。ダッシュボードのエディターではまだ `cooldownMs` と `waitForCooldownMs` を設定できません。後続の UI 作業が完了するまでは、構成ファイルまたは管理 API を使用してください。 diff --git a/docs-site/src/content/docs/ko/guides/combos.md b/docs-site/src/content/docs/ko/guides/combos.md index c9ccf3757b3..21edf34cd25 100644 --- a/docs-site/src/content/docs/ko/guides/combos.md +++ b/docs-site/src/content/docs/ko/guides/combos.md @@ -194,7 +194,7 @@ v1/base/v2 모드와 암호화된 작업의 전체 흐름은 [Sub-agent Surface] ### 대시보드 -로컬 대시보드를 열고 **Models → Combos**를 선택합니다. 워크스페이스는 콤보를 만들고, 편집하고, 이름을 바꾸고, 제거할 수 있으며, 대상 선택기에서는 비활성 모델과 중첩 콤보를 제외합니다. +로컬 대시보드를 열고 **Models → Combos**를 선택합니다. 워크스페이스에서 콤보를 만들고, 편집하고, 이름을 바꾸고, 제거할 수 있습니다. 모델 선택기에는 숨겨진 모델도 포함됩니다. 표시 설정은 외부 모델 목록에만 적용되며 콤보 내부 선택에는 영향을 주지 않습니다. 숨겨진 모델을 선택해도 외부에 표시되지 않습니다. 대상 선택기에서는 비활성 공급자, 중첩 콤보, 결정 서비스 전용 JEV 공급자를 계속 제외합니다. 각 대상에는 **사용 가능**, **할당량 소진**, **할당량 알 수 없음** 실시간 배지도 표시됩니다. 편집기는 사용 가능한 모든 대상에 대해 설정된 인증 정보의 추론 한도가 소진되었다는 서버 확인이 현재 유효할 때만 할당량을 이유로 저장과 만들기를 차단합니다. 표시 전용 계정·모델·검색·MCP 할당량이나 누락되거나 만료된 라우팅 근거 정보는 이 차단을 일으키지 않습니다. 차단은 해당 한도의 초기화 시점이나 데이터 유효기간이 끝나면 해제되며 페이지가 활성화되거나 표시될 때 다시 확인됩니다. 새로 고침은 콤보 데이터와 할당량을 모두 다시 불러옵니다. 대시보드 편집기에서는 아직 `cooldownMs`나 `waitForCooldownMs`를 설정할 수 없습니다. 후속 UI 작업이 완료될 때까지 구성 파일이나 관리 API를 사용하세요. diff --git a/docs-site/src/content/docs/ru/guides/combos.md b/docs-site/src/content/docs/ru/guides/combos.md index fe966d0cbf9..894f39ca8c7 100644 --- a/docs-site/src/content/docs/ru/guides/combos.md +++ b/docs-site/src/content/docs/ru/guides/combos.md @@ -234,7 +234,7 @@ Failover намеренно ограничен. Он помогает при п ### Дашборд Откройте локальный дашборд и выберите **Models → Combos**. Рабочая область умеет создавать, редактировать, -переименовывать и удалять combo, а селектор целей исключает отключённые модели и вложенные combo. +переименовывать и удалять combo. Селекторы моделей включают скрытые модели: видимость влияет только на внешние списки, а не на внутренний выбор в combo. Выбор скрытой модели не делает её видимой снаружи. Селектор целей по-прежнему исключает отключённых провайдеров, вложенные combo и провайдеров JEV, предназначенных только для сервиса принятия решений. У каждой цели также отображается актуальный значок квоты: **Доступно**, **Квота исчерпана** или **Квота неизвестна**. Редактор блокирует сохранение и создание из-за квоты только тогда, когда для каждой пригодной к использованию цели есть действующее подтверждение сервера об исчерпании лимита инференса для настроенных учётных данных. Квоты аккаунта, модели, поиска и MCP, предназначенные только для отображения, а также отсутствующие или просроченные данные для принятия решения о маршрутизации не вызывают эту блокировку. Блокировка истекает при соответствующем сбросе квоты или окончании срока актуальности данных и проверяется повторно, когда страница становится активной или видимой; «Обновить» повторно загружает и данные combo, и квоты. Редактор дашборда пока не предоставляет `cooldownMs` и `waitForCooldownMs`; до появления соответствующего UI используйте файл конфигурации или Management API. diff --git a/docs-site/src/content/docs/tr/guides/combos.md b/docs-site/src/content/docs/tr/guides/combos.md index d2c3be63d49..e5d8dc05083 100644 --- a/docs-site/src/content/docs/tr/guides/combos.md +++ b/docs-site/src/content/docs/tr/guides/combos.md @@ -305,8 +305,9 @@ Arayüzü](/tr/guides/sub-agent-surface/) sayfasına bakın. Yerel kontrol panelini açın ve **Modeller → Kombolar** seçeneğini belirleyin. Çalışma alanı komboları oluşturur, düzenler, yeniden adlandırır ve kaldırır; -hedef seçicisi ise devre dışı bırakılmış modelleri ve iç içe geçmiş komboları -hariç tutar. +model seçicileri gizli modelleri de içerir. Görünürlük yalnızca dış model listelerini etkiler, +kombo içindeki seçimi etkilemez; gizli bir modeli seçmek onu dışarıda görünür yapmaz. +Hedef seçicisi devre dışı sağlayıcıları, iç içe komboları ve yalnızca karar hizmeti sunan JEV sağlayıcılarını hariç tutar. Her hedef ayrıca canlı bir kota rozeti gösterir: **Kullanılabilir**, **Kota tükendi** veya **Kota bilinmiyor**. Düzenleyici, kota nedeniyle Kaydet ve Oluştur işlemlerini yalnızca kullanılabilir hedeflerin tümü için yapılandırılmış kimlik bilgisine ait çıkarım sınırının tükendiğini doğrulayan geçerli sunucu bilgisi varsa engeller. Yalnızca görüntüleme amaçlı hesap, model, arama ve MCP kotaları ya da eksik veya süresi dolmuş yönlendirme kanıtları bu engellemeye neden olmaz. Engelleme, ilgili sıfırlama zamanında veya verinin güncellik süresi dolduğunda sona erer ve sayfa etkin ya da görünür olduğunda yeniden kontrol edilir; Yenile, hem kombo verilerini hem de kotaları yeniden yükler. 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 d0513216b58..7ae396add05 100644 --- a/docs-site/src/content/docs/zh-cn/guides/combos.md +++ b/docs-site/src/content/docs/zh-cn/guides/combos.md @@ -214,7 +214,7 @@ combo 失败分为 **跳转** 失败和 **终止** 失败。 ### Dashboard -打开本地 dashboard 并选择 **Models → Combos**。该工作区可以创建、编辑、重命名和删除 combo,其目标选择器会排除已禁用的模型和嵌套 combo。 +打开本地 dashboard 并选择 **Models → Combos**。该工作区可以创建、编辑、重命名和删除 combo。模型选择器使用包含隐藏模型的完整目录:显示/隐藏设置只影响对外模型列表,不影响 Combo 内部选择,选中隐藏模型也不会将其对外显示。目标选择器仍排除已禁用的提供方、嵌套 combo 和仅用于决策服务的 JEV 提供方。 每个目标还会显示实时额度徽章:**可用**、**额度已用尽**或**额度未知**。只有当每个可用目标均有当前有效的服务器确认,表明其所配置凭据的推理限额已耗尽时,编辑器才会因额度而禁止保存和创建。仅供显示的账户、模型、搜索和 MCP 额度,以及缺失或已过期的路由依据,都不会触发此限制。此限制会在适用的重置时间或数据有效期结束时解除,并在页面变为活动或可见状态时重新检查;刷新会同时重新加载 Combo 数据和额度。dashboard 编辑器目前还不能设置 `cooldownMs` 或 `waitForCooldownMs`;在后续 UI 完成前,请使用配置文件或管理 API。 diff --git a/docs-site/src/content/docs/zh-tw/guides/combos.md b/docs-site/src/content/docs/zh-tw/guides/combos.md index 3fdb413a417..bb70ccceb52 100644 --- a/docs-site/src/content/docs/zh-tw/guides/combos.md +++ b/docs-site/src/content/docs/zh-tw/guides/combos.md @@ -221,7 +221,7 @@ Codex v2 子代理有一個重要限制([issue #92](https://github.com/lidge-j ### 儀表板 -開啟本機儀表板並選擇 **Combos**。該工作區可建立、編輯、重新命名與移除 combo,且其目標 picker 會排除已停用的模型與巢狀 combo。 +開啟本機儀表板並選擇 **Combos**。該工作區可建立、編輯、重新命名與移除 combo。模型選擇器包含隱藏模型;顯示/隱藏設定僅影響對外模型清單,不影響 Combo 內部選擇,選取隱藏模型也不會將其對外顯示。目標選擇器仍排除已停用的供應商、巢狀 combo 與僅提供決策服務的 JEV 供應商。 每個目標也會顯示即時額度徽章:**可用**、**額度已用盡**或**額度未知**。只有當每個可用目標均有目前有效的伺服器確認,顯示其所設定憑證的推論限額已耗盡時,編輯器才會因配額而停用儲存與建立。僅供顯示的帳戶、模型、搜尋與 MCP 配額,以及缺失或已過期的路由依據,都不會觸發此限制。此限制會在適用的重設時間或資料有效期限結束時解除,並在頁面變為作用中或可見狀態時重新檢查;重新整理會同時重新載入 Combo 資料與配額。 diff --git a/gui/src/pages/Combos.tsx b/gui/src/pages/Combos.tsx index c7cd9194727..a760fff0515 100644 --- a/gui/src/pages/Combos.tsx +++ b/gui/src/pages/Combos.tsx @@ -83,7 +83,7 @@ export default function Combos({ onCountChange?: (count: number) => void; }) { const t = useT(); - const cacheKey = `ocx.combos.workspace.v1:${apiBase}`; + const cacheKey = `ocx.combos.workspace.v2:${apiBase}`; const cached = useMemo(() => seedCombos(cacheKey), [cacheKey]); /* @@ -207,7 +207,7 @@ export default function Combos({ ...(inputModalities && inputModalities.length > 0 ? { inputModalities } : {}), }; capabilityModels.push(option); - if (model.disabled !== true) models.push(option); + models.push(option); // Catalog visibility does not restrict internal Combo selection. } // Add a default placeholder only when the catalog has no row, including disabled rows. diff --git a/gui/tests/combo-capability-hidden-targets.test.tsx b/gui/tests/combo-capability-hidden-targets.test.tsx index e4d7c75bec2..012d0e24860 100644 --- a/gui/tests/combo-capability-hidden-targets.test.tsx +++ b/gui/tests/combo-capability-hidden-targets.test.tsx @@ -9,7 +9,7 @@ import { readSessionListCacheEntry, writeSessionListCacheEntry } from "../src/se import { parseComboList } from "../src/combo-workspace-data"; const API_BASE = "http://localhost"; -const CACHE_KEY = `ocx.combos.workspace.v1:${API_BASE}`; +const CACHE_KEY = `ocx.combos.workspace.v2:${API_BASE}`; const globals = ["document", "window", "navigator", "localStorage", "sessionStorage", "IS_REACT_ACT_ENVIRONMENT"] as const; let previousGlobals: Record<(typeof globals)[number], unknown>; let testWindow: Window; @@ -190,7 +190,7 @@ test("JEV selected-target effort controls use the disabled row's advertised ladd expect(container.textContent).not.toContain("Reasoning efforts unknown"); }); -test("a disabled provider default never returns in the new-member picker", async () => { +test("hidden models including the provider default appear once in new and existing member pickers", async () => { mockCatalog([hiddenImage, { ...hiddenImage, id: "visible", disabled: false }], [target(hiddenImage)], {}, { vision: "hidden" }); await mount(); await openDetail(); @@ -199,8 +199,67 @@ test("a disabled provider default never returns in the new-member picker", async const dialog = container.querySelector("dialog")!; await chooseProvider("vision", dialog); const options = [...dialog.querySelector('select[aria-label="Model"]')!.options].map(option => option.value); - expect(options).toEqual(["", "visible"]); - expect(options).not.toContain("hidden"); + expect(options).toEqual(["", "hidden", "visible"]); + const model = dialog.querySelector('select[aria-label="Model"]')!; + await act(async () => { + model.value = "hidden"; + model.dispatchEvent(new testWindow.Event("change", { bubbles: true })); + }); + expect(model.value).toBe("hidden"); + expectImages(true, dialog); +}); + +test("all-hidden catalogs remain selectable and old filtered caches are ignored", async () => { + writeSessionListCacheEntry(`ocx.combos.workspace.v1:${API_BASE}`, { + combos: parseComboList({ combos: [combo([target(hiddenImage)])] }), + providers: [{ name: "vision" }], models: [], cataloguedComboIds: ["hidden-test"], + }); + const api = mockCatalog([hiddenImage, { ...hiddenImage, id: "other" }], [target(hiddenImage)]); + await mount(); + expect(api.requests()).toBe(1); + await act(async () => { container.querySelector('button[aria-label="Add combo"]')!.click(); }); + const dialog = container.querySelector("dialog")!; + await chooseProvider("vision", dialog); + expect([...dialog.querySelector('select[aria-label="Model"]')!.options].map(option => option.value)) + .toEqual(["", "hidden", "other"]); +}); + +test("editing can select and save a hidden model without changing its visibility", async () => { + const visible = { ...hiddenImage, id: "visible", disabled: false }; + mockCatalog([hiddenImage, visible], [target(visible)]); + const fetchCatalog = globalThis.fetch; + const writes: { path: string; body: Record }[] = []; + globalThis.fetch = (async (input: RequestInfo | URL, init?: RequestInit) => { + if (init?.method === "PUT") { + writes.push({ path: new URL(String(input)).pathname, body: JSON.parse(String(init.body)) }); + return Response.json({ success: true }); + } + return fetchCatalog(input, init); + }) as typeof fetch; + await mount(); + await openDetail(); + const model = container.querySelector('select[aria-label="Model"]')!; + expect([...model.options].map(option => option.value)).toEqual(["", "hidden", "visible"]); + await act(async () => { + model.value = "hidden"; + model.dispatchEvent(new testWindow.Event("change", { bubbles: true })); + }); + const save = container.querySelector("#cwi-edit-save")!; + expect(save.disabled).toBe(false); + await act(async () => { save.click(); }); + expect(writes).toHaveLength(1); + expect(writes[0]).toMatchObject({ path: "/api/combos", body: { combo: { targets: [target(hiddenImage)] } } }); + expect(hiddenImage.disabled).toBe(true); +}); + +test("JEV decision model suggestions include hidden models but exclude the JEV combo itself", async () => { + mockCatalog([hiddenImage], [target(hiddenImage)], { strategy: "jev", decisionModel: "vision/hidden" }); + await mount(); + await openDetail(); + const routes = [...container.querySelectorAll('datalist[id$="-decision-model-options"] option')] + .map(option => option.value); + expect(routes).toContain("vision/hidden"); + expect(routes).not.toContain("combo/hidden-test"); }); test("a default missing from the catalog still gets a picker placeholder", async () => { @@ -222,7 +281,7 @@ test("cache remount and live revalidation preserve hidden-target capabilities", expect(cached.data.capabilityModels).toEqual([{ provider: "vision", id: "hidden", inputModalities: ["text", "image"], reasoningEfforts: ["low", "high"], }]); - expect(cached.data.models).toEqual([]); + expect(cached.data.models).toEqual(cached.data.capabilityModels); await act(async () => { root!.unmount(); }); root = undefined; clearClientResourceStoresForTests(); diff --git a/gui/tests/page-loading-contract.test.tsx b/gui/tests/page-loading-contract.test.tsx index d12d9b589c3..45f117e3618 100644 --- a/gui/tests/page-loading-contract.test.tsx +++ b/gui/tests/page-loading-contract.test.tsx @@ -123,7 +123,7 @@ let testWindow: Window; const originalFetch = globalThis.fetch; const API_BASE = "http://localhost"; -const CACHE_KEY = `ocx.combos.workspace.v1:${API_BASE}`; +const CACHE_KEY = `ocx.combos.workspace.v2:${API_BASE}`; const CACHED_PAGE = { combos: [], providers: [{ name: "openai", disabled: false, hiddenFromPicker: false, authMode: "forward", adapter: "openai", baseUrl: "https://api.openai.com/v1", defaultModel: "gpt-5" }], diff --git a/structure/dashboard-and-usage.md b/structure/dashboard-and-usage.md index 3cbebeb3a11..a3508d79d28 100644 --- a/structure/dashboard-and-usage.md +++ b/structure/dashboard-and-usage.md @@ -461,12 +461,12 @@ Chat helper admission in `src/server/responses/core.ts` follows the [deferred st ## Combo editor routing quota -`src/server/management/provider-routes.ts` projects `routingQuota` after each quota read using the -current provider configuration and [scoped inference evidence](runtime.md#scoped-provider-quota-for-combo-selection). -The DTO carries only state, observation time and an exclusive `validUntil`; cached display reports -and private credential bindings are unchanged. Known states expire within 30 minutes; exhaustion -may expire earlier when the runtime predicate clears at a reset boundary, accounting for other -windows and persistent USD blockers. +`gui/src/pages/Combos.tsx` supplies all non-Combo management catalog rows to internal model selectors, including hidden models. Catalog visibility does not restrict Combo member or JEV decision-model selection and selecting a hidden model does not change its external visibility. Disabled-provider, decision-service and recursion exclusions remain unchanged; the v2 workspace cache avoids reusing v1 visibility-filtered candidates. + +`src/server/management/provider-routes.ts` projects `routingQuota` after each quota read using the current provider +configuration and [scoped inference evidence](runtime.md#scoped-provider-quota-for-combo-selection). The DTO carries only state, +observation time and an exclusive `validUntil`; cached display reports and private credential bindings are unchanged. Known states +expire within 30 minutes; exhaustion may expire earlier when the runtime predicate clears at a reset boundary, accounting for other windows and persistent USD blockers. `gui/src/combo-workspace-data.ts` accepts only this projection for quota-based Save/Create blocking. Missing, invalid or expired evidence is unknown. `gui/src/pages/Combos.tsx` wakes at the rendered