From a4e8a0e1a7731832de066e10d5f9447b0f59d575 Mon Sep 17 00:00:00 2001 From: Epinephrine <27862058+luvs01@users.noreply.github.com> Date: Mon, 19 Oct 2026 20:03:00 +0900 Subject: [PATCH 01/50] feat: add opt-in plaintext V2 subagent mode Select V2 through model catalog pins while enabling the existing plaintext task-message compiler. Require explicit CLI/API/UI acknowledgement, preserve legacy preferences and existing chat history, and verify runtime support and write receipts. Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../docs/fr/guides/subagent-v1-default.md | 4 + .../content/docs/guides/sub-agent-surface.md | 3 +- .../docs/guides/subagent-v1-default.md | 16 +- .../docs/ja/guides/subagent-v1-default.md | 4 + .../docs/ko/guides/subagent-v1-default.md | 14 +- .../docs/ko/reference/configuration/agents.md | 4 +- .../docs/reference/configuration/agents.md | 6 +- .../docs/ru/guides/subagent-v1-default.md | 4 + .../docs/tr/guides/subagent-v1-default.md | 4 + .../docs/zh-cn/guides/subagent-v1-default.md | 4 + .../docs/zh-tw/guides/subagent-v1-default.md | 4 + .../components/PlaintextV2ConsentModal.tsx | 90 ++++++ .../SubagentDelegationSection.tsx | 6 +- gui/src/i18n/de.ts | 8 + gui/src/i18n/en.ts | 8 + gui/src/i18n/fr.ts | 8 + gui/src/i18n/ja.ts | 8 + gui/src/i18n/ko.ts | 8 + gui/src/i18n/pt.ts | 8 + gui/src/i18n/ru.ts | 8 + gui/src/i18n/tr.ts | 8 + gui/src/i18n/vi.ts | 8 + gui/src/i18n/zh-TW.ts | 8 + gui/src/i18n/zh.ts | 8 + gui/src/pages/Models.tsx | 50 +-- gui/src/pages/Subagents.tsx | 44 ++- gui/src/pages/dashboard-core-poll.ts | 19 +- gui/src/pages/dashboard-dialogs.tsx | 11 +- gui/src/pages/dashboard-overview-head.tsx | 6 +- gui/src/pages/models-settings-summary.ts | 3 +- gui/src/pages/models-shared.ts | 31 +- gui/src/pages/use-dashboard-data.ts | 50 ++- gui/src/pages/use-subagent-delegation.ts | 22 +- gui/src/subagent-surface.ts | 24 +- gui/tests/dashboard-plaintext-v2.test.tsx | 242 ++++++++++++++ gui/tests/plaintext-v2-consent.test.tsx | 211 +++++++++++++ scripts/test-layout/layout.json | 2 +- .../references/01_surface_agents-routing.md | 3 +- src/cli/capabilities-agents-routing.ts | 4 +- src/cli/v2-input.ts | 16 +- src/cli/v2-local-output.ts | 5 +- src/cli/v2-runtime.ts | 13 +- src/cli/v2.ts | 13 +- src/codex/catalog/parsing.ts | 14 +- src/codex/catalog/retained-sync.ts | 3 +- src/codex/convergence.ts | 4 +- src/codex/inject/multi-agent-v2.ts | 12 +- src/config/multi-agent-surface.ts | 33 +- src/link/codex-settings.ts | 2 +- src/server/index/serve-options.ts | 5 +- src/server/index/startup-warnings.ts | 8 +- .../management/agent-settings-routes.ts | 56 +++- src/server/responses/core-normalize.ts | 3 +- src/types/config.ts | 9 +- structure/cli-management.md | 2 + structure/gui-and-management-api.md | 10 +- structure/remote-link.md | 2 + structure/subagent-capacity.md | 15 +- structure/subagents.md | 6 +- tests/cli/cli-v2-plaintext.test.ts | 84 +++++ tests/clients/link-home-settings.test.ts | 9 + .../codex-inject-v1-reconcile.test.ts | 30 ++ .../multi-agent-v2-plaintext.test.ts | 296 ++++++++++++++++++ tests/fixtures/test-layout-expected.json | 2 +- tests/server/config.test.ts | 33 ++ ...plaintext-v2-agent-messages-server.test.ts | 4 +- 66 files changed, 1528 insertions(+), 134 deletions(-) create mode 100644 gui/src/components/PlaintextV2ConsentModal.tsx create mode 100644 gui/tests/dashboard-plaintext-v2.test.tsx create mode 100644 gui/tests/plaintext-v2-consent.test.tsx create mode 100644 tests/cli/cli-v2-plaintext.test.ts create mode 100644 tests/codex-integration/multi-agent-v2-plaintext.test.ts diff --git a/docs-site/src/content/docs/fr/guides/subagent-v1-default.md b/docs-site/src/content/docs/fr/guides/subagent-v1-default.md index d93aa422837..b5d63ed8113 100644 --- a/docs-site/src/content/docs/fr/guides/subagent-v1-default.md +++ b/docs-site/src/content/docs/fr/guides/subagent-v1-default.md @@ -3,6 +3,10 @@ title: Pourquoi v1 est la surface de sous-agent par défaut description: Ce que bloque la limite des tâches chiffrées en v2, pourquoi OpenCodex livre désormais v1 et comment utiliser malgré tout v2. --- +**Option expérimentale : `v2-plaintext`.** Ce mode choisit V2 pour les nouvelles conversations sans activer la priorité V2 globale. Après accord explicite, les nouveaux messages de tâche éligibles sont transmis en clair au niveau applicatif ; HTTPS et l’authentification restent actifs. Le texte peut rester dans l’historique, les requêtes du fournisseur sélectionné et les données de débogage locales. Les tâches déjà chiffrées ne sont pas déchiffrées. + +La commande est `ocx v2 mode v2-plaintext --live --acknowledge-plaintext-agent-messages`. Les limitations ci-dessous concernent le chemin V2 chiffré sans cette option ; les modes existants restent disponibles. + OpenCodex s'installe avec la surface de sous-agent réglée sur **v1**. Les pages Dashboard, Models et Subagents demandent toutes confirmation avant de passer à **base** ou **v2** et renvoient vers cette page. La CLI ne demande rien. diff --git a/docs-site/src/content/docs/guides/sub-agent-surface.md b/docs-site/src/content/docs/guides/sub-agent-surface.md index 67ec9f1a8ad..ce66de9d4b1 100644 --- a/docs-site/src/content/docs/guides/sub-agent-surface.md +++ b/docs-site/src/content/docs/guides/sub-agent-surface.md @@ -360,8 +360,7 @@ tier that Codex converts to `max`; opencodex then maps or clamps the value for t The model context cap is independent of sub-agent mode. Configure it on the Models page; native OpenAI models retain their real context windows. -The experimental `plaintextV2AgentMessages` field is unset in a fresh config and runs only when set -to `true`. The caller must use the Responses wire, and the final destination must use +The experimental `plaintextV2AgentMessages` field is unset in a fresh config. Explicit `true` or the separately selected `v2-plaintext` mode enables the compiler. That mode also selects V2 for new chats without enabling the global override; leaving it preserves the independent field. The caller must use the Responses wire, and the final destination must use `adapter: "openai-responses"`, `authMode: "forward"`, and the exact base URL `https://chatgpt.com/backend-api/codex`. OpenAI API-key providers, custom compatible gateways, routes to other providers, and non-Responses callers are excluded. For an eligible new native diff --git a/docs-site/src/content/docs/guides/subagent-v1-default.md b/docs-site/src/content/docs/guides/subagent-v1-default.md index ab8c6654c6f..28d1d927b70 100644 --- a/docs-site/src/content/docs/guides/subagent-v1-default.md +++ b/docs-site/src/content/docs/guides/subagent-v1-default.md @@ -7,9 +7,21 @@ OpenCodex installs with the sub-agent surface set to **v1**. The Dashboard, Mode pages all ask you to confirm before moving to **base** or **v2**, and this page is what that confirmation links to. The CLI does not prompt. -The reason is narrow and specific: on v2, a task handed from a ChatGPT-native model to a routed +The reason is narrow and specific: on the unchanged encrypted v2 path, a task handed from a ChatGPT-native model to a routed model cannot be read by the routed model. That is the single most common way people delegate — -a GPT parent spawning a Grok, Claude or GLM child — and on v2 it fails every time. +a GPT parent spawning a Grok, Claude or GLM child. An unreadable encrypted task fails at that boundary. The experimental opt-in below changes how new task messages are produced; it does not decrypt existing ones. + +## Optional V2 with plaintext task messages + +**V2 · plaintext tasks** (`v2-plaintext`) selects V2 for new chats while leaving the global V2 override off. It preserves V2 task names, hierarchy, follow-up messages, cancellation and concurrency, and reuses the experimental plaintext compiler for eligible canonical ChatGPT Responses requests. Existing chats retain their recorded selection. This is an additional explicit option; installing an update does not select it for you. + +The dashboard requires acknowledgment that task text can remain in Codex history, selected-provider requests and local debug state. HTTPS and authentication remain enabled. No recovery model call is added. Existing ciphertext remains unchanged, and unsupported backend/client behavior can still fail. The ordinary v1 and mixed native-v1/routed-v2 modes remain available. + +```bash +ocx v2 mode v2-plaintext --live --acknowledge-plaintext-agent-messages +``` + +Omit `--live` to configure the local target. Read `ocx v2 status --live --json` afterward; configured state and catalog convergence are distinct from a successful real delegation. To leave this mode, explicitly choose another mode. Independently set `plaintextV2AgentMessages` values are preserved. ## What you see when it happens diff --git a/docs-site/src/content/docs/ja/guides/subagent-v1-default.md b/docs-site/src/content/docs/ja/guides/subagent-v1-default.md index ab3814d4e71..5f00dd4dbc1 100644 --- a/docs-site/src/content/docs/ja/guides/subagent-v1-default.md +++ b/docs-site/src/content/docs/ja/guides/subagent-v1-default.md @@ -3,6 +3,10 @@ title: サブエージェントの既定サーフェスが v1 である理由 description: v2 の暗号化タスクの制約で何が動かなくなるか、OpenCodex が v1 を既定とする理由、v2 を使う方法を説明します。 --- +**実験的な選択肢: `v2-plaintext`。** グローバル V2 強制を有効にせず、新しいチャットで V2 を選択します。明示的な確認後、対象となる新しいタスクメッセージをアプリケーション層の平文で送信します。HTTPS と認証は維持されます。タスクの本文は履歴、選択したプロバイダーへのリクエスト、ローカルのデバッグ状態に残る可能性があります。既存の暗号文は復号しません。 + +コマンドは `ocx v2 mode v2-plaintext --live --acknowledge-plaintext-agent-messages` です。以下の制限は、この選択肢を使わない暗号化 V2 経路に関するものです。従来のモードも利用できます。 + OpenCodex をインストールすると、サブエージェントのサーフェスは **v1** に設定されます。Dashboard、Models、Subagents の各ページでは、**base** または **v2** に移る前に確認を求め、その確認からこのページにリンクします。CLI では確認は表示されません。 理由は明確です。v2 では、ChatGPT ネイティブモデルからルーティングされたモデルへ渡すタスクを、受け取ったモデルが読めません。GPT の親から Grok、Claude、GLM の子を作るのは一般的な委任方法ですが、v2 では毎回失敗します。 diff --git a/docs-site/src/content/docs/ko/guides/subagent-v1-default.md b/docs-site/src/content/docs/ko/guides/subagent-v1-default.md index dbf8fee9103..9beb5249007 100644 --- a/docs-site/src/content/docs/ko/guides/subagent-v1-default.md +++ b/docs-site/src/content/docs/ko/guides/subagent-v1-default.md @@ -5,7 +5,19 @@ description: v2의 암호화 작업 제한으로 생기는 문제, opencodex가 opencodex를 설치하면 서브에이전트 서피스가 **v1**로 설정됩니다. Dashboard, Models, Subagents 페이지에서 **base**나 **v2**로 바꾸려면 확인을 요청하며, 그 확인 창에서 이 문서로 연결됩니다. CLI에서는 확인 메시지가 나오지 않습니다. -이유는 분명합니다. v2에서 ChatGPT 네이티브 모델이 라우팅된 모델에 맡긴 작업은 라우팅된 모델이 읽을 수 없습니다. GPT 부모가 Grok, Claude, GLM 자식을 만드는 것은 가장 흔한 위임 방식인데, v2에서는 매번 실패합니다. +이유는 암호화된 v2 전달 경로에 있습니다. ChatGPT 네이티브 모델이 만든 작업 암호문은 다른 제공자가 읽을 수 없어 경계에서 실패합니다. 아래 실험적 선택지는 새 작업 메시지를 평문으로 생성하도록 바꾸며, 기존 암호문을 복호화하지 않습니다. + +## 선택 기능: V2와 평문 작업 전달 + +**V2 · 평문 작업 전달**(`v2-plaintext`)은 전역 V2 강제를 끈 상태에서 새 채팅의 기본 선택을 V2로 지정합니다. V2 작업 이름·계층·후속 메시지·취소·동시 실행 기능을 유지하고, 조건에 맞는 공식 ChatGPT Responses 전달에 기존 실험적 평문 컴파일러를 사용합니다. 기존 채팅은 기록된 선택을 유지합니다. 업데이트만으로 이 모드가 선택되지는 않습니다. + +대시보드에서는 작업 텍스트가 Codex 이력, 선택한 제공자의 요청, 로컬 디버그 상태에 평문으로 남을 수 있음을 확인해야 합니다. HTTPS와 인증은 유지되며 복구용 모델 호출을 추가하지 않습니다. 기존 암호문은 그대로이고, 지원되지 않는 백엔드·클라이언트 동작은 실패할 수 있습니다. 일반 v1 및 네이티브 v1/라우팅 v2 혼합 모드도 유지됩니다. + +```bash +ocx v2 mode v2-plaintext --live --acknowledge-plaintext-agent-messages +``` + +로컬 설정을 변경하려면 `--live`를 빼세요. 이후 `ocx v2 status --live --json`으로 적용값을 확인하세요. 설정 저장·카탈로그 반영과 실제 위임 성공은 별도 검증입니다. 다른 모드를 명시적으로 선택하면 이 모드에서 나옵니다. 별도로 설정한 `plaintextV2AgentMessages` 값은 보존됩니다. ## 문제가 생기면 보이는 것 diff --git a/docs-site/src/content/docs/ko/reference/configuration/agents.md b/docs-site/src/content/docs/ko/reference/configuration/agents.md index 60a0af24f67..4b2248e8c62 100644 --- a/docs-site/src/content/docs/ko/reference/configuration/agents.md +++ b/docs-site/src/content/docs/ko/reference/configuration/agents.md @@ -3,13 +3,15 @@ title: 에이전트 설정 description: 멀티 에이전트 표면, 위임 안내, 선호 모델, 대체 체인, 기본값 동기화, 노력 상한을 다룹니다. --- +`multiAgentMode: "v2-plaintext"`는 새 채팅에 V2를 선택하면서 전역 V2 강제를 끄고, 조건에 맞는 공식 ChatGPT Responses 경로에 실험적 평문 작업 전달을 적용하는 명시적 선택입니다. 대시보드 확인 또는 `ocx v2 mode v2-plaintext --live --acknowledge-plaintext-agent-messages`로 진입합니다. 작업 텍스트는 이력·선택한 제공자의 요청·로컬 디버그 상태에 남을 수 있지만 HTTPS와 인증은 유지됩니다. 기존 암호문을 복호화하지 않으며 기존 채팅 기록도 변경하지 않습니다. 별도 `plaintextV2AgentMessages` 설정은 다른 모드로 돌아갈 때도 보존됩니다. + 에이전트 설정은 어떤 Codex 협업 표면을 노출할지와, opencodex가 위임 작업을 어떻게 안내하고, 라우팅하고, 제한할지를 제어합니다. ## 에이전트 필드 | 필드 | 형식 | 기본값 | 의미 | | --- | --- | --- | --- | -| `multiAgentMode?` | `"v1" \| "default" \| "v2"` | `"default"` | `v1`은 카탈로그의 모든 모델에 v1을 표시하고, `v2`는 모든 모델에 v2를 표시합니다. `default`는 상위 고정값(Sol/Terra는 v2, Luna는 v1)을 복원하고, 그 외에는 네이티브 `multi_agent_v2` 플래그를 따릅니다. 새 세션에 적용됩니다. | +| `multiAgentMode?` | `"v1" \| "default" \| "v2" \| "v2-plaintext"` | `"default"` | `v1`은 카탈로그의 모든 모델에 v1을 표시하고, `v2`는 모든 모델에 v2를 표시합니다. `default`는 상위 고정값(Sol/Terra는 v2, Luna는 v1)을 복원하고, 그 외에는 네이티브 `multi_agent_v2` 플래그를 따릅니다. `v2-plaintext`는 실험적 평문 작업 전달을 함께 켜고 전역 강제는 끕니다. 새 세션에 적용됩니다. | | `subagentModels?` | `string[]` | `gpt-6-astra`, `gpt-6.1-sol`, `gpt-6-luna` | 최대 다섯 개의 bare native id, account-qualified `/` id 또는 routed `provider/model` id를 서브에이전트 선택기에서 우선 표시합니다. Subagents 페이지는 bare native와 routed id만 제공하며 저장할 때 exact account-qualified 선택을 제외합니다. exact 선택은 `ocx agent subagents set`을 사용하거나 설정을 직접 편집하세요. [Astra 최초 업그레이드](/reference/configuration/agents/#astra-roster-upgrade) 이후에는 빈 목록도 그대로 보존됩니다. | | `injectionModel?` | `string` | — | 프록시가 작성한 v2 위임 안내에서 사용하는 선호 네이티브 또는 라우팅된 서브에이전트 모델입니다. | | `injectionEffort?` | `string` | — | 선호 노력(`low`부터 `ultra`까지)입니다. `injectionModel`이 있을 때만 의미가 있습니다. | diff --git a/docs-site/src/content/docs/reference/configuration/agents.md b/docs-site/src/content/docs/reference/configuration/agents.md index ed106c598e0..7afa269a34f 100644 --- a/docs-site/src/content/docs/reference/configuration/agents.md +++ b/docs-site/src/content/docs/reference/configuration/agents.md @@ -45,7 +45,7 @@ still depends on upstream support for your account. | Field | Type | Default | Meaning | | --- | --- | --- | --- | -| `multiAgentMode?` | `"v1" \| "default" \| "v2"` | `"default"` | `v1` stamps every catalog model as v1; `v2` stamps every model as v2. `default` restores upstream pins (Sol/Terra v2, Luna v1) and otherwise follows the native `multi_agent_v2` flag. Applies to new sessions. | +| `multiAgentMode?` | `"v1" \| "default" \| "v2" \| "v2-plaintext"` | `"default"` | `v1` stamps every catalog model as v1; `v2` stamps every model as v2. `default` restores upstream pins (Sol/Terra v2, Luna v1) and otherwise follows the native `multi_agent_v2` flag. `v2-plaintext` also enables experimental plaintext task delivery and disables the global override. Applies to new sessions. | | `keepNativeChatGptOnV1?` | `boolean` | `false` | When `multiAgentMode` is `"v2"`, disable the global V2 override, stamp ChatGPT-native rows as v1, and keep routed rows on v2. Codex resolves the global override before catalog pins, so both parts are required for a ChatGPT parent to spawn routed children without backend-encrypted tasks ([#92](https://github.com/lidge-jun/opencodex/issues/92)). Ignored in `v1` and `default`. | | `subagentModels?` | `string[]` | `gpt-6-astra`, `gpt-6.1-sol`, `gpt-6-luna` | Up to five bare native, account-qualified `/`, or routed `provider/model` ids featured first in the sub-agent picker. The dashboard offers only bare native and routed ids and omits exact account-qualified choices when it saves; use `ocx agent subagents set` or edit the configuration for exact choices. After the [one-time Astra upgrade](#astra-roster-upgrade), an explicit empty list is preserved. | | `injectionModel?` | `string` | — | Preferred native or routed sub-agent model used in proxy-authored v2 delegation guidance. | @@ -59,7 +59,7 @@ still depends on upstream support for your account. | `codexRoleTiers?` | `{ fast?, standard?, frontier?: string[] }` | — | Capability tier of named models for [role auto-assign](/guides/integrations/#auto-assign). Listed models take that tier; other models are tiered by price, and unpriced unlisted models are never proposed. | | `effortCap?` | `string` | — | Hard ceiling for qualifying v2 main turns and marked spawned-child turns. Accepts `low` through `ultra`. | | `subagentEffortCap?` | `string` | — | Additional ceiling for spawned-child turns only. When both caps apply, the lower wins. | -| `plaintextV2AgentMessages?` | `boolean` | — (unset) | Experimental opt-in. It runs only when explicitly set to `true` and asks eligible native ChatGPT v2 parents to emit `spawn_agent`, `send_message`, and `followup_task` message arguments as plaintext. See [Plaintext v2 agent messages](#plaintext-v2-agent-messages). | +| `plaintextV2AgentMessages?` | `boolean` | — (unset) | Experimental opt-in. Explicit `true` or `multiAgentMode: "v2-plaintext"` asks eligible native ChatGPT v2 parents to emit `spawn_agent`, `send_message`, and `followup_task` message arguments as plaintext. See [Plaintext v2 agent messages](#plaintext-v2-agent-messages). | | `agentTaskRecovery?` | `object` | — | Experimental opt-in recovery for backend-encrypted v2 tasks sent to routed providers. Disabled unless `enabled: true`; see [Encrypted v2 task recovery](#encrypted-v2-task-recovery). | Manage the surface with the dashboard or @@ -187,7 +187,7 @@ on a mid-thread model switch. ## Plaintext v2 agent messages -`plaintextV2AgentMessages` is unset in a fresh config and runs only when explicitly set to `true`. +`plaintextV2AgentMessages` is unset in a fresh config. Explicit `true` or the separately selected `multiAgentMode: "v2-plaintext"` enables this experimental path. The mode also sets V2 catalog pins while disabling the global override; existing chat history is not rewritten. Enter through the dashboard confirmation or `ocx v2 mode v2-plaintext --live --acknowledge-plaintext-agent-messages`. The mode does not overwrite the independent boolean, so leaving it restores that boolean's prior behavior. The caller must use the Responses wire, and the final destination must use the canonical ChatGPT Codex forward transport: `adapter: "openai-responses"`, `authMode: "forward"`, and the exact base URL `https://chatgpt.com/backend-api/codex`. OpenAI API-key providers, custom OpenAI-compatible diff --git a/docs-site/src/content/docs/ru/guides/subagent-v1-default.md b/docs-site/src/content/docs/ru/guides/subagent-v1-default.md index 8cbc5f3a39a..23179e92206 100644 --- a/docs-site/src/content/docs/ru/guides/subagent-v1-default.md +++ b/docs-site/src/content/docs/ru/guides/subagent-v1-default.md @@ -3,6 +3,10 @@ title: Почему v1 — режим подагентов по умолчани description: Что ломает ограничение шифрованных задач v2, почему OpenCodex теперь поставляется с v1 и что делать, если вам нужен v2. --- +**Экспериментальный вариант: `v2-plaintext`.** Он выбирает V2 для новых чатов, не включая глобальное принудительное V2. После явного подтверждения новые подходящие сообщения задач передаются открытым текстом на уровне приложения; HTTPS и аутентификация сохраняются. Текст может оставаться в истории, запросах выбранному провайдеру и локальных отладочных данных. Уже зашифрованные задачи не расшифровываются. + +Команда: `ocx v2 mode v2-plaintext --live --acknowledge-plaintext-agent-messages`. Ограничения ниже относятся к зашифрованному пути V2 без этой опции. Прежние режимы остаются доступны. + OpenCodex устанавливается с режимом подагентов **v1**. Страницы Dashboard, Models и Subagents просят подтверждение перед переходом на **base** или **v2** и ссылаются на эту страницу. CLI подтверждения не запрашивает. diff --git a/docs-site/src/content/docs/tr/guides/subagent-v1-default.md b/docs-site/src/content/docs/tr/guides/subagent-v1-default.md index 11ebaff1e56..78697a8be67 100644 --- a/docs-site/src/content/docs/tr/guides/subagent-v1-default.md +++ b/docs-site/src/content/docs/tr/guides/subagent-v1-default.md @@ -3,6 +3,10 @@ title: v1 neden varsayılan alt ajan arayüzü? description: v2 şifreli görev sınırlamasının neyi bozduğu, OpenCodex'in neden artık v1 sunduğu ve yine de v2 istiyorsanız ne yapacağınız. --- +**Deneysel seçenek: `v2-plaintext`.** Genel V2 zorlamasını açmadan yeni sohbetler için V2 seçer. Açık onaydan sonra uygun yeni görev mesajları uygulama düzeyinde düz metin olarak aktarılır; HTTPS ve kimlik doğrulama korunur. Görev metni geçmişte, seçilen sağlayıcıya gönderilen isteklerde ve yerel hata ayıklama verilerinde kalabilir. Mevcut şifreli görevlerin şifresi çözülmez. + +Komut: `ocx v2 mode v2-plaintext --live --acknowledge-plaintext-agent-messages`. Aşağıdaki sınırlamalar bu seçenek kullanılmadan yapılan şifreli V2 aktarımı içindir. Önceki modlar kullanılabilir. + OpenCodex, alt ajan arayüzü **v1** olarak kurulur. Dashboard, Models ve Subagents sayfaları **base** veya **v2** moduna geçmeden önce onay ister; bu sayfa da o onayın bağlantı hedefidir. CLI onay istemez. diff --git a/docs-site/src/content/docs/zh-cn/guides/subagent-v1-default.md b/docs-site/src/content/docs/zh-cn/guides/subagent-v1-default.md index 58fa0890419..a70923942a0 100644 --- a/docs-site/src/content/docs/zh-cn/guides/subagent-v1-default.md +++ b/docs-site/src/content/docs/zh-cn/guides/subagent-v1-default.md @@ -3,6 +3,10 @@ title: 为什么 v1 是默认子代理界面 description: v2 加密任务限制会破坏什么、OpenCodex 为何现在默认使用 v1,以及仍想使用 v2 时该怎么办。 --- +**实验性选项:`v2-plaintext`。** 此模式为新聊天选择 V2,但不启用全局 V2 强制。明确确认后,符合条件的新任务消息以应用层明文传递;HTTPS 和身份验证保持启用。任务文本可能保留在历史记录、发送给所选提供商的请求和本地调试数据中。已有密文不会被解密。 + +命令为 `ocx v2 mode v2-plaintext --live --acknowledge-plaintext-agent-messages`。下文的限制针对未使用此选项的加密 V2 路径,原有模式仍可使用。 + OpenCodex 安装后的子代理界面默认为 **v1**。Dashboard、Models 和 Subagents 页面在切换到 **base** 或 **v2** 前都会要求确认,并链接到本页。CLI 不会提示。 原因明确而具体:在 v2 上,从 ChatGPT 原生模型分派给路由模型的任务无法被路由模型读取。这恰恰是最常见的委派方式——GPT 父代理启动 Grok、Claude 或 GLM 子代理——而在 v2 上每次都会失败。 diff --git a/docs-site/src/content/docs/zh-tw/guides/subagent-v1-default.md b/docs-site/src/content/docs/zh-tw/guides/subagent-v1-default.md index bdab9f109f1..fc38871b257 100644 --- a/docs-site/src/content/docs/zh-tw/guides/subagent-v1-default.md +++ b/docs-site/src/content/docs/zh-tw/guides/subagent-v1-default.md @@ -3,6 +3,10 @@ title: 為什麼 v1 是預設子代理介面 description: v2 加密任務限制會造成什麼問題、OpenCodex 為何改以 v1 發布,以及仍想使用 v2 時的選擇。 --- +**實驗性選項:`v2-plaintext`。** 此模式為新聊天選擇 V2,但不啟用全域 V2 強制。明確確認後,符合條件的新工作訊息以應用層明文傳遞;HTTPS 與驗證保持啟用。工作文字可能保留在歷史紀錄、傳送給所選供應商的請求和本機除錯資料中。既有密文不會被解密。 + +指令為 `ocx v2 mode v2-plaintext --live --acknowledge-plaintext-agent-messages`。下文的限制針對未使用此選項的加密 V2 路徑,原有模式仍可使用。 + OpenCodex 安裝後,子代理介面預設為 **v1**。Dashboard、Models 與 Subagents 頁面在切換至 **base** 或 **v2** 前都會要求確認,並連結至本頁說明。CLI 不會提示。 原因很明確:在 v2 上,由 ChatGPT 原生模型交給路由模型的任務,路由模型無法讀取。這恰好是最常見的委派方式,即 GPT 父代理啟動 Grok、Claude 或 GLM 子代理,而在 v2 上每次都會失敗。 diff --git a/gui/src/components/PlaintextV2ConsentModal.tsx b/gui/src/components/PlaintextV2ConsentModal.tsx new file mode 100644 index 00000000000..9a2a0feab74 --- /dev/null +++ b/gui/src/components/PlaintextV2ConsentModal.tsx @@ -0,0 +1,90 @@ +/** + * Consent dialog for the 'v2-plaintext' sub-agent surface. + * + * The mode sends native v2 collaboration task text to ChatGPT without the encryption + * marker, so entering it requires an explicit acknowledgement on the same PUT. The + * checkbox is that acknowledgement: nothing is written until it is checked and the + * operator confirms. It deliberately replaces the base/v2 warning dialog for this + * mode, so one confirmation covers the experimental/plaintext notice and the usual + * "v1 is the reliable default" guidance instead of asking twice in a row. + */ +import { useCallback, useEffect, useId, useRef, useState } from "react"; +import { useT } from "../i18n/shared"; +import { IconAlert } from "../icons"; + +export default function PlaintextV2ConsentModal({ + busy = false, + onConfirm, + onDismiss, +}: { + busy?: boolean; + /** Checkbox checked plus confirm clicked: send the mode with its acknowledgement. */ + onConfirm: () => void; + /** Escape or backdrop: abandon the selection, keep the stored mode. */ + onDismiss: () => void; +}) { + const t = useT(); + const titleId = useId(); + const bodyId = useId(); + const dialogRef = useRef(null); + const [acknowledged, setAcknowledged] = useState(false); + + // Open as a native modal dialog — focus trapping and the backdrop come from the platform. + useEffect(() => { + const dialog = dialogRef.current; + if (dialog && !dialog.open) dialog.showModal(); + }, []); + + const handleCancel = useCallback((e: React.SyntheticEvent) => { + e.preventDefault(); + // Escape could otherwise close the dialog over a request that then lands anyway. + if (busy) return; + onDismiss(); + }, [busy, onDismiss]); + + return ( + + + + + + + ); +} diff --git a/gui/src/components/subagents-workspace/SubagentDelegationSection.tsx b/gui/src/components/subagents-workspace/SubagentDelegationSection.tsx index d75d0664f70..e4e8becf540 100644 --- a/gui/src/components/subagents-workspace/SubagentDelegationSection.tsx +++ b/gui/src/components/subagents-workspace/SubagentDelegationSection.tsx @@ -11,6 +11,7 @@ import { Select, Tooltip } from "../../ui"; import { IconArrowDown, IconArrowUp, IconInfo, IconX } from "../../icons"; import { useT, type TKey } from "../../i18n/shared"; import { formatNamespacedModelId } from "../../provider-icons"; +import { subagentSurfaceModes } from "../../subagent-surface"; import type { DelegationPatch, DelegationModelOption } from "../../pages/use-subagent-delegation"; import type { UltraModePatch, UltraModeState } from "../../pages/use-subagent-delegation"; import DelegationSuggest from "./DelegationSuggest"; @@ -278,10 +279,13 @@ export default function SubagentDelegationSection({ {t("models.v2DocsLink")} + {ultraMode.plaintextAgentMessagesEnabled === true && ( +
{t("models.v2PlaintextOn")}
+ )}
- {(["v1", "default", "v2"] as const).map(mode => ( + {subagentSurfaceModes(ultraMode.supportsPlaintextV2Mode === true).map(mode => (
{v2 && ( @@ -2091,6 +2092,7 @@ export default function Models({ apiBase, restartEpoch = 0, connected = false, c {t("models.v2Conflict")} )} {v2Note && {v2Note}} + {v2.plaintextAgentMessagesEnabled === true && {t("models.v2PlaintextOn")}}
)} diff --git a/gui/src/pages/Subagents.tsx b/gui/src/pages/Subagents.tsx index b56e6199702..55e843164c1 100644 --- a/gui/src/pages/Subagents.tsx +++ b/gui/src/pages/Subagents.tsx @@ -8,7 +8,8 @@ import { readSessionListCache, writeSessionListCache } from "../session-list-cac import { useDataSurface } from "../data-surface"; import { DataSurfaceSkeleton } from "../components/data-surface"; import SubagentSurfaceWarningModal from "../components/SubagentSurfaceWarningModal"; -import { SUBAGENT_SURFACE_GUIDE_URL } from "../subagent-surface"; +import PlaintextV2ConsentModal from "../components/PlaintextV2ConsentModal"; +import { SUBAGENT_SURFACE_GUIDE_URL, confirmsPlaintextV2Mode, type SubagentSurfaceMode } from "../subagent-surface"; import { useSubagentDelegation, type UltraModePatch, type UltraModeState } from "./use-subagent-delegation"; type CachedSubagents = { available: string[]; chosen: string[]; fallback?: string[]; pollMs?: number; fallbackAvailable?: string[] }; @@ -65,7 +66,7 @@ export default function Subagents({ apiBase }: { apiBase: string }) { * for. Switching endpoints with the dialog open must not apply one proxy's answer to another, * and tagging beats clearing it from an effect, which would be a cascading render. */ - const [pendingSurface, setPendingSurface] = useState<{ mode: "default" | "v2"; apiBase: string } | null>(null); + const [pendingSurface, setPendingSurface] = useState<{ mode: "default" | "v2" | "v2-plaintext"; apiBase: string } | null>(null); const [ultraLoadFailed, setUltraLoadFailed] = useState(false); const ultraLoadGeneration = useRef(0); const currentUltraApiBase = useRef(apiBase); @@ -83,10 +84,12 @@ export default function Subagents({ apiBase }: { apiBase: string }) { const res = await fetch(`${apiBase}/api/v2`, { signal }); const data = await readJsonOrThrow<{ enabled?: boolean; - multiAgentMode?: "v1" | "default" | "v2"; + multiAgentMode?: SubagentSurfaceMode; multiAgentModeHintText?: string | null; keepNativeChatGptOnV1?: boolean; multiAgentModeHintRecommendation?: { text?: unknown; revision?: unknown }; + supportsPlaintextV2Mode?: boolean; + plaintextAgentMessagesEnabled?: boolean; }>(res, t("sub.ultraModeLoadFail")); if (!data) return false; if (signal?.aborted || generation !== ultraLoadGeneration.current || currentUltraApiBase.current !== apiBase) return false; @@ -108,8 +111,15 @@ export default function Subagents({ apiBase }: { apiBase: string }) { // Ultra mode replaces Codex's effort-derived policy for every model. The // `default` surface still preserves upstream V1 pins (for example luna), // so only an explicitly forced V2 catalog is an effective surface here. - multiAgentV2Enabled: data.enabled === true && data.multiAgentMode === "v2", - multiAgentMode: data.multiAgentMode ?? "default", + // 'v2-plaintext' is also a forced V2 surface: every catalog row is pinned v2 + // while the global feature stays off. + multiAgentV2Enabled: (data.enabled === true && data.multiAgentMode === "v2") + || data.multiAgentMode === "v2-plaintext", + multiAgentMode: data.multiAgentMode === "v1" || data.multiAgentMode === "v2" || data.multiAgentMode === "v2-plaintext" + ? data.multiAgentMode + : "default", + supportsPlaintextV2Mode: data.supportsPlaintextV2Mode === true, + plaintextAgentMessagesEnabled: data.plaintextAgentMessagesEnabled === true, } }); return true; }, [apiBase, t]); @@ -139,7 +149,10 @@ export default function Subagents({ apiBase }: { apiBase: string }) { headers: { "Content-Type": "application/json" }, body: JSON.stringify(patch), }); - await readJsonOrThrow(res, t("sub.ultraModeSaveFail")); + const receipt = await readJsonOrThrow(res, t("sub.ultraModeSaveFail")); + if (patch.multiAgentMode === "v2-plaintext" && !confirmsPlaintextV2Mode(receipt)) { + throw new Error(t("sub.ultraModeSaveFail")); + } if (currentUltraApiBase.current !== requestApiBase || !await loadUltraMode()) return; setOk(true); setStatus(t("sub.ultraModeSaved")); @@ -391,8 +404,10 @@ export default function Subagents({ apiBase }: { apiBase: string }) { ultraSaving: ultraSaving || !ultraModeCurrent, // This page carries the same v1/base/v2 switch as Models and the Dashboard, so it // needs the same gate: base and v2 wait for an answer, everything else writes. + // 'v2-plaintext' also waits — for its own consent dialog, which stages the + // plaintext acknowledgement onto the same request as the mode. onUltraModeSave: patch => { - if (patch.multiAgentMode === "default" || patch.multiAgentMode === "v2") { + if (patch.multiAgentMode === "default" || patch.multiAgentMode === "v2" || patch.multiAgentMode === "v2-plaintext") { setPendingSurface({ mode: patch.multiAgentMode, apiBase }); return; } @@ -402,7 +417,20 @@ export default function Subagents({ apiBase }: { apiBase: string }) { onUltraModeRetry: () => { void retryUltraMode(); }, }} /> - {pendingSurface && pendingSurface.apiBase === apiBase && ( + {pendingSurface && pendingSurface.apiBase === apiBase && pendingSurface.mode === "v2-plaintext" && ( + { + setPendingSurface(null); + void saveUltraMode({ + multiAgentMode: "v2-plaintext", + plaintextAgentMessagesAcknowledged: true, + }); + }} + onDismiss={() => setPendingSurface(null)} + /> + )} + {pendingSurface && pendingSurface.apiBase === apiBase && pendingSurface.mode !== "v2-plaintext" && ( false. */ + supportsPlaintextV2: boolean; }; export type DashboardEpochRefs = { @@ -241,16 +243,17 @@ export async function fetchDashboardMaMode( ): Promise { try { const v2Res = await fetch(`${apiBase}/api/v2`, { signal }); - if (!v2Res.ok) return { maMode: "default" }; - const v2Data = await v2Res.json() as { multiAgentMode?: unknown; multiAgentSurfaceAdvisory?: unknown }; + if (!v2Res.ok) return { maMode: "default", supportsPlaintextV2: false }; + const v2Data = await v2Res.json() as { multiAgentMode?: unknown; multiAgentSurfaceAdvisory?: unknown; supportsPlaintextV2Mode?: unknown }; const advisory = readSubagentSurfaceAdvisory(v2Data.multiAgentSurfaceAdvisory); - if (v2Data.multiAgentMode === "v1" || v2Data.multiAgentMode === "v2") { - return { maMode: v2Data.multiAgentMode, advisory }; + const supportsPlaintextV2 = v2Data.supportsPlaintextV2Mode === true; + if (v2Data.multiAgentMode === "v1" || v2Data.multiAgentMode === "v2" || v2Data.multiAgentMode === "v2-plaintext") { + return { maMode: v2Data.multiAgentMode, advisory, supportsPlaintextV2 }; } - return { maMode: "default", advisory }; + return { maMode: "default", advisory, supportsPlaintextV2 }; } catch (error) { if (isAbortError(error, signal)) throw error; - return { maMode: "default" }; + return { maMode: "default", supportsPlaintextV2: false }; } } diff --git a/gui/src/pages/dashboard-dialogs.tsx b/gui/src/pages/dashboard-dialogs.tsx index f0f10050680..a87624880e8 100644 --- a/gui/src/pages/dashboard-dialogs.tsx +++ b/gui/src/pages/dashboard-dialogs.tsx @@ -7,6 +7,7 @@ import { import type { useDashboardData } from "./use-dashboard-data"; import { shadowSourceModelLabel } from "./shadow-call-source"; import SubagentSurfaceWarningModal from "../components/SubagentSurfaceWarningModal"; +import PlaintextV2ConsentModal from "../components/PlaintextV2ConsentModal"; import { SUBAGENT_SURFACE_GUIDE_URL } from "../subagent-surface"; type Dash = ReturnType; @@ -213,7 +214,15 @@ export function DashboardDialogs(d: Dash) { - {(pendingMaMode || maAdvisoryOpen) && ( + {pendingMaMode === "v2-plaintext" ? ( + // Dedicated consent: one checkbox-gated PUT covers the experimental plaintext notice + // and the mode write; the generic advisory warning stays for every other reason. + { void keepMaMode(); }} + onDismiss={dismissMaSurfaceDialog} + /> + ) : (pendingMaMode || maAdvisoryOpen) && ( ) { +}: Pick) { const t = useT(); const online = health?.status === "ok"; @@ -51,7 +53,7 @@ export function DashboardOverviewHead({
- {(["v1", "default", "v2"] as const).map(mode => ( + {subagentSurfaceModes(maSupportsPlaintextV2).map(mode => (