From 1851c124d5b65453f2642f5968f7f7a28770b0e1 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 6 Nov 2026 13:09:45 +0000 Subject: [PATCH] feat(app-shell): Connect an Agent marks cloud-connected ways in as not available on a non-public address claude.ai (web) and Doubao Work's cloud tasks reach only a public HTTPS address. On an http:// address, localhost, a single-label or .local host, or a private, loopback or link-local IP, both now show a note with the reason and what makes them available; the local clients are unchanged. Co-Authored-By: Claude Claude-Session: https://claude.ai/code/session_01L9qibzWZSWpaweunqGyeA9 --- ...30897-connect-agent-unreachable-address.md | 11 +++ .../ConnectAgentWidget.keyName-13355.test.tsx | 93 +++++++++++++++++-- .../console/connect/ConnectAgentWidget.tsx | 55 ++++++++++- .../src/console/connect/deploymentReach.ts | 66 +++++++++++++ ui/packages/i18n/src/locales/ar.ts | 7 ++ ui/packages/i18n/src/locales/de.ts | 7 ++ ui/packages/i18n/src/locales/en.ts | 7 ++ ui/packages/i18n/src/locales/es.ts | 7 ++ ui/packages/i18n/src/locales/fr.ts | 7 ++ ui/packages/i18n/src/locales/ja.ts | 7 ++ ui/packages/i18n/src/locales/ko.ts | 7 ++ ui/packages/i18n/src/locales/pt.ts | 7 ++ ui/packages/i18n/src/locales/ru.ts | 7 ++ ui/packages/i18n/src/locales/zh.ts | 7 ++ 14 files changed, 286 insertions(+), 9 deletions(-) create mode 100644 .changeset/30897-connect-agent-unreachable-address.md create mode 100644 ui/packages/app-shell/src/console/connect/deploymentReach.ts diff --git a/.changeset/30897-connect-agent-unreachable-address.md b/.changeset/30897-connect-agent-unreachable-address.md new file mode 100644 index 00000000000..f5d18af8be5 --- /dev/null +++ b/.changeset/30897-connect-agent-unreachable-address.md @@ -0,0 +1,11 @@ +--- +'@object-ui/app-shell': patch +'@object-ui/i18n': patch +--- + +feat(app-shell): Connect an Agent marks claude.ai (web) and Doubao cloud tasks as not available on an address that is not public HTTPS + +Clause-②: no +Metadata-protocol: none + +claude.ai on the web and Doubao Work's cloud tasks connect from their vendor's servers, which reach only a public HTTPS address. When the address Connect an Agent shows is an `http://` address, `localhost`, a single-label or `.local` host, or a private, loopback or link-local IP, those two ways in now carry a "not available for this deployment" note with the reason in one sentence (naming the address) and what makes them available (serving the system at a public HTTPS address). Claude Desktop, Claude Code, Codex, Cursor and the other clients stay as they are, and on a public HTTPS address nothing changes. The five new strings ship in all ten console languages. diff --git a/ui/packages/app-shell/src/console/connect/ConnectAgentWidget.keyName-13355.test.tsx b/ui/packages/app-shell/src/console/connect/ConnectAgentWidget.keyName-13355.test.tsx index 2f33d537a78..122437dcaf0 100644 --- a/ui/packages/app-shell/src/console/connect/ConnectAgentWidget.keyName-13355.test.tsx +++ b/ui/packages/app-shell/src/console/connect/ConnectAgentWidget.keyName-13355.test.tsx @@ -20,11 +20,14 @@ * The key form lives in the collapsed "Advanced: scripts and CI" section, so * those cases open it first. The last block holds the page's shape: the * endpoint and SKILL.md on top, a picker that shows one client's steps and - * remembers the choice on this browser, and the key form collapsed. + * remembers the choice on this browser, and the key form collapsed. The + * reachability block holds that claude.ai (web) and Doubao Work's cloud tasks + * are marked not available on an address that is not public HTTPS. * - * `fetch` is stubbed by path; `t` echoes the KEY (with the `name` option - * appended when one is passed), as the sibling suites do, so the assertions - * read which string the widget asked for and with which name. + * `fetch` is stubbed by path; `t` echoes the KEY (with the `name` or + * `address` option appended when one is passed), as the sibling suites do, + * so the assertions read which string the widget asked for and with which + * value. */ import '@testing-library/jest-dom/vitest'; @@ -34,17 +37,22 @@ import { cleanup, fireEvent, render, screen, waitFor } from '@testing-library/re vi.mock('@object-ui/i18n', async (importOriginal) => ({ ...(await importOriginal>()), useObjectTranslation: () => ({ - t: (key: string, opts?: { name?: string }) => - opts && typeof opts.name === 'string' ? `«${key}|${opts.name}»` : `«${key}»`, + t: (key: string, opts?: { name?: string; address?: string }) => { + const arg = opts?.name ?? opts?.address; + return typeof arg === 'string' ? `«${key}|${arg}»` : `«${key}»`; + }, language: 'en', }), })); import { ConnectAgent } from './ConnectAgentWidget'; +import { isPublicHttpsAddress } from './deploymentReach'; const MINTED_KEY = 'osk_test_0123456789'; let fetchMock: ReturnType; +/** The `routes.mcp` discovery answers; a case may point it at another address. */ +let discoveredMcp = '/api/v1/mcp'; /** The bodies of every `POST /api/v1/keys` the widget sent. */ function keyPosts(): unknown[] { @@ -55,10 +63,11 @@ function keyPosts(): unknown[] { beforeEach(() => { window.localStorage.clear(); + discoveredMcp = '/api/v1/mcp'; fetchMock = vi.fn(async (input: RequestInfo | URL, init?: RequestInit) => { const url = String(input); if (url.includes('/api/v1/discovery')) { - return new Response(JSON.stringify({ data: { routes: { mcp: '/api/v1/mcp' } } }), { + return new Response(JSON.stringify({ data: { routes: { mcp: discoveredMcp } } }), { status: 200, headers: { 'content-type': 'application/json' }, }); @@ -279,3 +288,73 @@ describe('Connect an Agent asks which client the person uses and shows only thos expect(screen.getByText(CLAUDE_CODE)).toBeInTheDocument(); }); }); + +describe('Connect an Agent marks the cloud-connected ways in as not available on an address that is not public HTTPS', () => { + const notes = () => screen.queryAllByRole('note').map((n) => n.textContent ?? ''); + + it('§1 classifies the address: http, localhost, single-label and private hosts are not public HTTPS', () => { + for (const address of [ + 'http://crm.example.com/api/v1/mcp', + 'https://localhost:3000/api/v1/mcp', + 'https://app.localhost/api/v1/mcp', + 'https://crm/api/v1/mcp', + 'https://crm.local/api/v1/mcp', + 'https://10.1.2.3/api/v1/mcp', + 'https://172.20.0.5/api/v1/mcp', + 'https://192.168.1.10/api/v1/mcp', + 'https://127.0.0.1/api/v1/mcp', + 'https://169.254.1.1/api/v1/mcp', + 'https://100.64.0.1/api/v1/mcp', + 'https://[::1]/api/v1/mcp', + 'https://[fd12:3456::1]/api/v1/mcp', + 'https://[fe80::1]/api/v1/mcp', + 'https://[::ffff:10.0.0.1]/api/v1/mcp', + 'not a url', + ]) { + expect(isPublicHttpsAddress(address), address).toBe(false); + } + for (const address of [ + 'https://crm.example.com/api/v1/mcp', + 'https://crm.example.com:8443/api/v1/mcp', + 'https://8.8.8.8/api/v1/mcp', + 'https://172.32.0.1/api/v1/mcp', + 'https://[2001:db8::1]/api/v1/mcp', + ]) { + expect(isPublicHttpsAddress(address), address).toBe(true); + } + }); + + it('§2 on an http://localhost address, claude.ai (web) and Doubao cloud tasks say why and what fixes it; local clients unchanged', async () => { + discoveredMcp = 'http://localhost:3000/api/v1/mcp'; + await renderPage(); + expect(notes()).toEqual([ + '«connectAgent.unreachable.claudeWebTitle»«connectAgent.unreachable.claudeWebReason|http://localhost:3000»«connectAgent.unreachable.remedy»', + ]); + expect(screen.queryByText('«connectAgent.claude.reachability»')).not.toBeInTheDocument(); + // Claude Desktop and Claude Code keep their steps. + expect(screen.getByText('«connectAgent.claude.body»')).toBeInTheDocument(); + expect(screen.getByText(/^claude mcp add --transport http objectstack http:\/\/localhost:3000\/api\/v1\/mcp$/)).toBeInTheDocument(); + + pickClient('«connectAgent.doubao.title»'); + await screen.findByText('«connectAgent.doubao.body»'); + expect(notes()).toEqual([ + '«connectAgent.unreachable.doubaoCloudTitle»«connectAgent.unreachable.doubaoCloudReason|http://localhost:3000»«connectAgent.unreachable.remedy»', + ]); + + for (const local of ['Codex', 'Cursor']) { + pickClient(local); + await waitFor(() => expect(screen.getByRole('tab', { name: local })).toHaveAttribute('aria-selected', 'true')); + expect(notes()).toEqual([]); + } + }); + + it('§3 on a public HTTPS address nothing is marked', async () => { + discoveredMcp = 'https://crm.example.com/api/v1/mcp'; + await renderPage(); + expect(notes()).toEqual([]); + expect(screen.getByText('«connectAgent.claude.reachability»')).toBeInTheDocument(); + pickClient('«connectAgent.doubao.title»'); + await screen.findByText('«connectAgent.doubao.body»'); + expect(notes()).toEqual([]); + }); +}); diff --git a/ui/packages/app-shell/src/console/connect/ConnectAgentWidget.tsx b/ui/packages/app-shell/src/console/connect/ConnectAgentWidget.tsx index a5b9a50dc40..fb4225793de 100644 --- a/ui/packages/app-shell/src/console/connect/ConnectAgentWidget.tsx +++ b/ui/packages/app-shell/src/console/connect/ConnectAgentWidget.tsx @@ -20,7 +20,11 @@ * OAuth is the default story for every client (the deployment is its own * authorization server; you sign in as yourself); the Chinese clients' * steps name the API-key header as the fallback for a client version - * without the OAuth flow, + * without the OAuth flow. claude.ai (web) and Doubao Work's cloud tasks + * connect from their vendor's servers, so on an address that is not + * public HTTPS (`deploymentReach.ts`) both are marked not available, + * with the reason and what makes them available; the local clients stay + * as they are, * 4. API-key minting for headless callers (CI, machine accounts) via the * existing `POST /api/v1/keys`, in a collapsed "Advanced: scripts and CI" * section at the bottom — no client, so not a peer of the clients. @@ -62,6 +66,7 @@ import { TabsTrigger, } from '@object-ui/components'; import { + Ban, Bot, Check, ChevronRight, @@ -82,6 +87,7 @@ import { isConnectClientId, type ConnectClientId, } from './connectClientPreference.js'; +import { isPublicHttpsAddress } from './deploymentReach.js'; const SERVER_URL = (import.meta.env.VITE_SERVER_URL || '').replace(/\/$/, ''); @@ -154,6 +160,32 @@ function FieldRows({ rows }: { rows: Array<{ label: string; value: string }> }) ); } +/** The scheme and host a reader recognises (`http://localhost:3000`), not the full MCP path. */ +function addressOf(url: string): string { + try { + return new URL(url).origin; + } catch { + return url; + } +} + +/** + * A way in this deployment's address cannot offer: what is not available, + * why in one sentence, and what makes it available. + */ +function Unavailable({ title, reason, remedy }: { title: string; reason: string; remedy: string }) { + return ( +
+ +
+
{title}
+

{reason}

+

{remedy}

+
+
+ ); +} + /** The `mcpServers` JSON shape most clients import, with the client's own transport spelling. */ function mcpServersJson(type: string | null, url: string): string { const server = type ? { type, url } : { url }; @@ -198,6 +230,10 @@ export function ConnectAgent() { }, []); const skillUrl = mcpUrl ? `${mcpUrl}/skill` : null; + // claude.ai (web) and Doubao Work's cloud tasks connect from their vendor's + // servers: they reach only a public HTTPS address. + const cloudReachable = mcpUrl ? isPublicHttpsAddress(mcpUrl) : false; + const shownAddress = mcpUrl ? addressOf(mcpUrl) : ''; const cursorDeeplink = useMemo(() => { if (!mcpUrl) return null; @@ -324,7 +360,15 @@ export function ConnectAgent() {

claude.ai / Claude Desktop

{t('connectAgent.claude.body')}

-

{t('connectAgent.claude.reachability')}

+ {cloudReachable ? ( +

{t('connectAgent.claude.reachability')}

+ ) : ( + + )}

Claude Code

@@ -376,6 +420,13 @@ export function ConnectAgent() { ]} />

{t('connectAgent.doubao.hint')}

+ {!cloudReachable && ( + + )} diff --git a/ui/packages/app-shell/src/console/connect/deploymentReach.ts b/ui/packages/app-shell/src/console/connect/deploymentReach.ts new file mode 100644 index 00000000000..19c33aceb13 --- /dev/null +++ b/ui/packages/app-shell/src/console/connect/deploymentReach.ts @@ -0,0 +1,66 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +/** + * Whether a client that connects from its vendor's cloud (claude.ai on the + * web, Doubao Work's cloud tasks) can reach this deployment at the address + * Connect an Agent shows. Only a public HTTPS address is reachable from + * there: an `http://` address, `localhost`, a single-label or `.local` host + * name, and a loopback, private, link-local or carrier-grade NAT IP literal + * are not. A host name with a dot is taken as public: the page cannot see + * how the reader's DNS resolves it. + */ +export function isPublicHttpsAddress(address: string): boolean { + let url: URL; + try { + url = new URL(address); + } catch { + return false; + } + if (url.protocol !== 'https:') return false; + const host = url.hostname.toLowerCase().replace(/^\[|\]$/g, '').replace(/\.$/, ''); + if (!host) return false; + if (host.includes(':')) return !isNonPublicIpv6(host); + const octets = ipv4Octets(host); + if (octets) return !isNonPublicIpv4(octets); + if (host === 'localhost' || host.endsWith('.localhost')) return false; + if (!host.includes('.')) return false; + if (host.endsWith('.local') || host.endsWith('.internal')) return false; + return true; +} + +function ipv4Octets(host: string): number[] | null { + const parts = host.split('.'); + if (parts.length !== 4 || !parts.every((p) => /^\d{1,3}$/.test(p))) return null; + const octets = parts.map(Number); + return octets.every((n) => n <= 255) ? octets : null; +} + +function isNonPublicIpv4([a, b]: number[]): boolean { + return ( + a === 0 || + a === 10 || + a === 127 || + (a === 100 && b >= 64 && b <= 127) || + (a === 169 && b === 254) || + (a === 172 && b >= 16 && b <= 31) || + (a === 192 && b === 168) + ); +} + +function isNonPublicIpv6(host: string): boolean { + if (host === '::' || host === '::1') return true; + // IPv4-mapped (`::ffff:10.0.0.1`, or the hex form the URL parser writes). + const mapped = /^::ffff:(.+)$/.exec(host); + if (mapped) { + const dotted = ipv4Octets(mapped[1]); + if (dotted) return isNonPublicIpv4(dotted); + const hex = /^([0-9a-f]{1,4}):([0-9a-f]{1,4})$/.exec(mapped[1]); + if (hex) { + const hi = parseInt(hex[1], 16); + return isNonPublicIpv4([hi >> 8, hi & 0xff]); + } + } + const first = parseInt(host.split(':')[0] || '0', 16); + // fc00::/7 unique local, fe80::/10 link-local. + return (first & 0xfe00) === 0xfc00 || (first & 0xffc0) === 0xfe80; +} diff --git a/ui/packages/i18n/src/locales/ar.ts b/ui/packages/i18n/src/locales/ar.ts index 88a1349a8a4..c578d0efe05 100644 --- a/ui/packages/i18n/src/locales/ar.ts +++ b/ui/packages/i18n/src/locales/ar.ts @@ -4113,6 +4113,13 @@ const ar = { advanced: { title: 'متقدم: البرامج النصية وCI', }, + unreachable: { + claudeWebTitle: 'claude.ai (الويب): غير متاح لهذا النشر', + claudeWebReason: 'يتصل claude.ai من خوادم Anthropic، وهي لا تستطيع الوصول إلى {{address}}: لا يمكن الوصول من هناك إلا إلى عنوان HTTPS عام.', + doubaoCloudTitle: 'مهام السحابة: غير متاحة لهذا النشر', + doubaoCloudReason: 'يشغّل Doubao Work مهام السحابة من خوادمه الخاصة، وهي لا تستطيع الوصول إلى {{address}}: لا يمكن الوصول من هناك إلا إلى عنوان HTTPS عام.', + remedy: 'قدّم هذا النظام على عنوان HTTPS عام (https:// على نطاق يمكن الوصول إليه من الإنترنت) ليصبح متاحًا.', + }, apiKey: { title: 'مفاتيح API', badge: 'بدون واجهة', diff --git a/ui/packages/i18n/src/locales/de.ts b/ui/packages/i18n/src/locales/de.ts index 6cff500af5a..a237fa62d22 100644 --- a/ui/packages/i18n/src/locales/de.ts +++ b/ui/packages/i18n/src/locales/de.ts @@ -3808,6 +3808,13 @@ const de = { advanced: { title: 'Erweitert: Skripte und CI', }, + unreachable: { + claudeWebTitle: 'claude.ai (Web): für diese Bereitstellung nicht verfügbar', + claudeWebReason: 'claude.ai verbindet sich von den Servern von Anthropic, und diese erreichen {{address}} nicht: Von dort ist nur eine öffentliche HTTPS-Adresse erreichbar.', + doubaoCloudTitle: 'Cloud-Aufgaben: für diese Bereitstellung nicht verfügbar', + doubaoCloudReason: 'Doubao Work führt Cloud-Aufgaben auf den eigenen Servern aus, und diese erreichen {{address}} nicht: Von dort ist nur eine öffentliche HTTPS-Adresse erreichbar.', + remedy: 'Stellen Sie dieses System unter einer öffentlichen HTTPS-Adresse bereit (https:// auf einer aus dem Internet erreichbaren Domain), um es verfügbar zu machen.', + }, apiKey: { title: 'API-Schlüssel', badge: 'headless', diff --git a/ui/packages/i18n/src/locales/en.ts b/ui/packages/i18n/src/locales/en.ts index a0b1ccfc664..e95d4d38fc7 100644 --- a/ui/packages/i18n/src/locales/en.ts +++ b/ui/packages/i18n/src/locales/en.ts @@ -4615,6 +4615,13 @@ const en = { advanced: { title: 'Advanced: scripts and CI', }, + unreachable: { + claudeWebTitle: 'claude.ai (web): not available for this deployment', + claudeWebReason: 'claude.ai connects from Anthropic\'s servers, and they cannot reach {{address}}: only a public HTTPS address is reachable from there.', + doubaoCloudTitle: 'Cloud tasks: not available for this deployment', + doubaoCloudReason: 'Doubao Work runs cloud tasks from its own servers, and they cannot reach {{address}}: only a public HTTPS address is reachable from there.', + remedy: 'Serve this system at a public HTTPS address (https:// on a domain the internet can reach) to make it available.', + }, apiKey: { title: 'API keys', badge: 'headless', diff --git a/ui/packages/i18n/src/locales/es.ts b/ui/packages/i18n/src/locales/es.ts index d1c4c21ef10..72fa065e473 100644 --- a/ui/packages/i18n/src/locales/es.ts +++ b/ui/packages/i18n/src/locales/es.ts @@ -3885,6 +3885,13 @@ const es = { advanced: { title: 'Avanzado: scripts y CI', }, + unreachable: { + claudeWebTitle: 'claude.ai (web): no disponible para esta implementación', + claudeWebReason: 'claude.ai se conecta desde los servidores de Anthropic, y estos no pueden alcanzar {{address}}: desde allí solo se alcanza una dirección HTTPS pública.', + doubaoCloudTitle: 'Tareas en la nube: no disponibles para esta implementación', + doubaoCloudReason: 'Doubao Work ejecuta las tareas en la nube desde sus propios servidores, y estos no pueden alcanzar {{address}}: desde allí solo se alcanza una dirección HTTPS pública.', + remedy: 'Sirve este sistema en una dirección HTTPS pública (https:// en un dominio accesible desde Internet) para que esté disponible.', + }, apiKey: { title: 'Claves API', badge: 'headless', diff --git a/ui/packages/i18n/src/locales/fr.ts b/ui/packages/i18n/src/locales/fr.ts index b29e2fb286a..a51fdd4b346 100644 --- a/ui/packages/i18n/src/locales/fr.ts +++ b/ui/packages/i18n/src/locales/fr.ts @@ -3881,6 +3881,13 @@ const fr = { advanced: { title: 'Avancé : scripts et CI', }, + unreachable: { + claudeWebTitle: 'claude.ai (web) : indisponible pour ce déploiement', + claudeWebReason: 'claude.ai se connecte depuis les serveurs d’Anthropic, qui ne peuvent pas atteindre {{address}} : seule une adresse HTTPS publique est joignable depuis là-bas.', + doubaoCloudTitle: 'Tâches cloud : indisponibles pour ce déploiement', + doubaoCloudReason: 'Doubao Work exécute les tâches cloud depuis ses propres serveurs, qui ne peuvent pas atteindre {{address}} : seule une adresse HTTPS publique est joignable depuis là-bas.', + remedy: 'Servez ce système à une adresse HTTPS publique (https:// sur un domaine joignable depuis Internet) pour le rendre disponible.', + }, apiKey: { title: 'Clés API', badge: 'headless', diff --git a/ui/packages/i18n/src/locales/ja.ts b/ui/packages/i18n/src/locales/ja.ts index 5e17ff7c471..b919dd3f3a9 100644 --- a/ui/packages/i18n/src/locales/ja.ts +++ b/ui/packages/i18n/src/locales/ja.ts @@ -3810,6 +3810,13 @@ const ja = { advanced: { title: '詳細設定:スクリプトと CI', }, + unreachable: { + claudeWebTitle: 'claude.ai(Web 版):このデプロイでは利用できません', + claudeWebReason: 'claude.ai は Anthropic のサーバーから接続するため、{{address}} には到達できません。到達できるのは公開 HTTPS アドレスだけです。', + doubaoCloudTitle: 'クラウドタスク:このデプロイでは利用できません', + doubaoCloudReason: 'Doubao Work のクラウドタスクは自社サーバーから実行されるため、{{address}} には到達できません。到達できるのは公開 HTTPS アドレスだけです。', + remedy: 'このシステムを公開 HTTPS アドレス(インターネットから到達できるドメインの https://)で提供すると利用できます。', + }, apiKey: { title: 'API キー', badge: 'ヘッドレス', diff --git a/ui/packages/i18n/src/locales/ko.ts b/ui/packages/i18n/src/locales/ko.ts index bc8ef227910..9ce6a6a8eb2 100644 --- a/ui/packages/i18n/src/locales/ko.ts +++ b/ui/packages/i18n/src/locales/ko.ts @@ -3807,6 +3807,13 @@ const ko = { advanced: { title: '고급: 스크립트 및 CI', }, + unreachable: { + claudeWebTitle: 'claude.ai(웹): 이 배포에서는 사용할 수 없음', + claudeWebReason: 'claude.ai는 Anthropic 서버에서 연결하므로 {{address}}에 접근할 수 없습니다. 그곳에서는 공개 HTTPS 주소만 접근할 수 있습니다.', + doubaoCloudTitle: '클라우드 작업: 이 배포에서는 사용할 수 없음', + doubaoCloudReason: 'Doubao Work의 클라우드 작업은 자체 서버에서 실행되므로 {{address}}에 접근할 수 없습니다. 그곳에서는 공개 HTTPS 주소만 접근할 수 있습니다.', + remedy: '이 시스템을 공개 HTTPS 주소(인터넷에서 접근 가능한 도메인의 https://)로 제공하면 사용할 수 있습니다.', + }, apiKey: { title: 'API 키', badge: '헤드리스', diff --git a/ui/packages/i18n/src/locales/pt.ts b/ui/packages/i18n/src/locales/pt.ts index 61756348a1e..2b1b0b3f59c 100644 --- a/ui/packages/i18n/src/locales/pt.ts +++ b/ui/packages/i18n/src/locales/pt.ts @@ -3880,6 +3880,13 @@ const pt = { advanced: { title: 'Avançado: scripts e CI', }, + unreachable: { + claudeWebTitle: 'claude.ai (web): indisponível para esta implantação', + claudeWebReason: 'O claude.ai se conecta a partir dos servidores da Anthropic, e eles não alcançam {{address}}: de lá, só um endereço HTTPS público é alcançável.', + doubaoCloudTitle: 'Tarefas na nuvem: indisponíveis para esta implantação', + doubaoCloudReason: 'O Doubao Work executa as tarefas na nuvem nos próprios servidores, e eles não alcançam {{address}}: de lá, só um endereço HTTPS público é alcançável.', + remedy: 'Disponibilize este sistema em um endereço HTTPS público (https:// em um domínio acessível pela internet) para torná-lo disponível.', + }, apiKey: { title: 'Chaves de API', badge: 'headless', diff --git a/ui/packages/i18n/src/locales/ru.ts b/ui/packages/i18n/src/locales/ru.ts index 85cd00313fb..646e9eed83d 100644 --- a/ui/packages/i18n/src/locales/ru.ts +++ b/ui/packages/i18n/src/locales/ru.ts @@ -3971,6 +3971,13 @@ const ru = { advanced: { title: 'Дополнительно: скрипты и CI', }, + unreachable: { + claudeWebTitle: 'claude.ai (веб): недоступно для этого развертывания', + claudeWebReason: 'claude.ai подключается с серверов Anthropic, а они не могут достучаться до {{address}}: оттуда доступен только публичный HTTPS-адрес.', + doubaoCloudTitle: 'Облачные задачи: недоступны для этого развертывания', + doubaoCloudReason: 'Doubao Work выполняет облачные задачи на своих серверах, а они не могут достучаться до {{address}}: оттуда доступен только публичный HTTPS-адрес.', + remedy: 'Разместите систему по публичному HTTPS-адресу (https:// на домене, доступном из интернета), чтобы это стало доступно.', + }, apiKey: { title: 'API-ключи', badge: 'headless', diff --git a/ui/packages/i18n/src/locales/zh.ts b/ui/packages/i18n/src/locales/zh.ts index 6ebdd540c92..b6e16593792 100644 --- a/ui/packages/i18n/src/locales/zh.ts +++ b/ui/packages/i18n/src/locales/zh.ts @@ -4014,6 +4014,13 @@ const zh = { advanced: { title: '高级:脚本与 CI', }, + unreachable: { + claudeWebTitle: 'claude.ai(网页版):此部署不可用', + claudeWebReason: 'claude.ai 从 Anthropic 的服务器发起连接,访问不到 {{address}}:那里只能访问公网 HTTPS 地址。', + doubaoCloudTitle: '云端任务:此部署不可用', + doubaoCloudReason: '豆包工作的云端任务从它自己的服务器运行,访问不到 {{address}}:那里只能访问公网 HTTPS 地址。', + remedy: '把本系统部署到公网 HTTPS 地址(互联网可访问的域名,https://)后即可使用。', + }, apiKey: { title: 'API 密钥', badge: '无头场景',