[Init] Initial commit - NetMesh terminal manager
Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled

This commit is contained in:
2026-09-13 18:24:01 +08:00
commit 3c72efcb7f
3255 changed files with 907009 additions and 0 deletions

View File

@@ -0,0 +1,249 @@
import React from "react";
import { ExternalLink, LogIn, LogOut, RefreshCw, RotateCcw, X } from "lucide-react";
import { useI18n } from "../../../../application/i18n/I18nProvider";
import { Button } from "../../../ui/button";
import { Switch } from "../../../ui/switch";
import { cn } from "../../../../lib/utils";
import type { AgentPathInfo, CodexAppServerStatus, CodexIntegrationStatus, CodexLoginSession } from "./types";
export const CodexConnectionCard: React.FC<{
pathInfo: AgentPathInfo | null;
isResolvingPath: boolean;
customPath: string;
onCustomPathChange: (path: string) => void;
onRecheckPath: () => void;
onResetPath: () => void;
integration: CodexIntegrationStatus | null;
loginSession: CodexLoginSession | null;
isLoading: boolean;
hasPendingCustomPath?: boolean;
error: string | null;
onRefresh: () => void;
onConnect: () => void;
onCancel: () => void;
onOpenUrl: () => void;
onLogout: () => void;
appServerRuntime: 'sdk' | 'app-server';
appServerStatus: CodexAppServerStatus | null;
onAppServerRuntimeChange: (runtime: 'sdk' | 'app-server') => void;
}> = ({
pathInfo,
isResolvingPath,
customPath,
onCustomPathChange,
onRecheckPath,
onResetPath,
integration,
loginSession,
isLoading,
hasPendingCustomPath = false,
error,
onRefresh,
onConnect,
onCancel,
onOpenUrl,
onLogout,
appServerRuntime,
appServerStatus,
onAppServerRuntimeChange,
}) => {
const { t } = useI18n();
const found = pathInfo?.available;
const customConfigIncomplete = Boolean(
integration?.state === "connected_custom_config"
&& integration.customConfig
&& integration.customConfig.envKey
&& !integration.customConfig.envKeyPresent
&& !integration.customConfig.hasHardcodedApiKey,
);
const status = isResolvingPath
? t('ai.codex.detecting')
: !found
? t('ai.codex.notFound')
: loginSession?.state === "running"
? t('ai.codex.awaitingLogin')
: integration?.state === "connected_chatgpt"
? t('ai.codex.connectedChatGPT')
: integration?.state === "connected_api_key"
? t('ai.codex.connectedApiKey')
: integration?.state === "connected_custom_config"
? customConfigIncomplete
? t('ai.codex.customConfigIncomplete')
: t('ai.codex.connectedCustomConfig')
: integration?.state === "not_logged_in"
? t('ai.codex.notConnected')
: t('ai.codex.statusUnknown');
const statusClassName = isResolvingPath
? "text-muted-foreground"
: !found
? "text-amber-500"
: loginSession?.state === "running"
? "text-amber-500"
: customConfigIncomplete
? "text-amber-500"
: integration?.isConnected
? "text-emerald-500"
: "text-muted-foreground";
const outputText = loginSession?.error
? loginSession.error
: loginSession?.output?.trim()
? loginSession.output.trim()
: integration?.rawOutput?.trim()
? integration.rawOutput.trim()
: "";
return (
<div className="rounded-lg border bg-card p-4 space-y-3">
<div className="flex items-start justify-between gap-4">
<p className="min-w-0 text-xs text-muted-foreground leading-5">
{t('ai.codex.description')}
</p>
<div className={cn("text-xs font-medium shrink-0", statusClassName)}>
{status}
</div>
</div>
{found && (
<div className="flex items-center gap-2 text-xs">
<span className="text-muted-foreground">{t('ai.codex.path')}</span>
<span className="font-mono text-foreground truncate">{pathInfo.path}</span>
{pathInfo.version && (
<>
<span className="text-muted-foreground">|</span>
<span className="text-muted-foreground">{pathInfo.version}</span>
</>
)}
</div>
)}
{!isResolvingPath && (
<div className="space-y-2">
{!found && (
<p className="text-xs text-amber-500">
{t('ai.codex.notFoundHint')}
</p>
)}
<div className="flex items-center gap-2">
<input
type="text"
value={customPath}
onChange={(e) => onCustomPathChange(e.target.value)}
placeholder={t('ai.codex.customPathPlaceholder')}
className="flex-1 h-8 rounded-md border border-input bg-background px-3 text-sm font-mono placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
/>
<Button variant="outline" size="sm" onClick={onRecheckPath} disabled={!customPath.trim()}>
<RefreshCw size={14} className="mr-1.5" />
{t('ai.codex.check')}
</Button>
<Button variant="ghost" size="sm" onClick={onResetPath} disabled={!customPath.trim()}>
<RotateCcw size={14} className="mr-1.5" />
{t('ai.codex.resetPath')}
</Button>
</div>
</div>
)}
{found && (
<div className="border-t border-border/40 pt-3 flex items-start justify-between gap-4">
<div className="min-w-0 space-y-1">
<div className="flex items-center gap-2">
<span className="text-sm font-medium">{t('ai.codex.appServer.title')}</span>
<span className="rounded border border-amber-500/30 bg-amber-500/10 px-1.5 py-0.5 text-[10px] font-medium text-amber-500">
{t('ai.codex.appServer.experimental')}
</span>
</div>
<p className="text-xs text-muted-foreground leading-5">
{t('ai.codex.appServer.description')}
</p>
{appServerStatus?.checking ? (
<p className="text-xs text-muted-foreground">{t('ai.codex.appServer.checking')}</p>
) : appServerStatus?.available ? (
<p className="text-xs text-emerald-500">{t('ai.codex.appServer.available')}</p>
) : appServerStatus?.error ? (
<p className="text-xs text-amber-500">{appServerStatus.error}</p>
) : null}
</div>
<Switch
checked={appServerRuntime === 'app-server'}
disabled={Boolean(appServerStatus?.checking) || (appServerRuntime === 'sdk' && appServerStatus?.available !== true)}
aria-label={t('ai.codex.appServer.title')}
onCheckedChange={(checked) => onAppServerRuntimeChange(checked ? 'app-server' : 'sdk')}
/>
</div>
)}
{/* Connection & login UI -- only when codex is detected */}
{found && (
<>
<div className="border-t border-border/40 pt-3 flex items-center gap-2 flex-wrap">
{loginSession?.state === "running" ? (
<>
<Button variant="default" size="sm" onClick={onOpenUrl} disabled={!loginSession.url}>
<ExternalLink size={14} className="mr-1.5" />
{t('ai.codex.openLogin')}
</Button>
<Button variant="outline" size="sm" onClick={onCancel}>
<X size={14} className="mr-1.5" />
{t('common.cancel')}
</Button>
</>
) : integration?.state === "connected_custom_config" ? (
// Nothing to log out of; config.toml is user-owned state.
null
) : integration?.isConnected ? (
<Button variant="outline" size="sm" onClick={onLogout} disabled={hasPendingCustomPath}>
<LogOut size={14} className="mr-1.5" />
{t('ai.codex.logout')}
</Button>
) : (
<Button variant="default" size="sm" onClick={onConnect} disabled={hasPendingCustomPath}>
<LogIn size={14} className="mr-1.5" />
{t('ai.codex.connectChatGPT')}
</Button>
)}
<Button variant="outline" size="sm" onClick={onRefresh} disabled={isLoading || hasPendingCustomPath}>
<RefreshCw size={14} className={cn("mr-1.5", isLoading && "animate-spin")} />
{t('ai.codex.refreshStatus')}
</Button>
</div>
{integration?.state === "connected_custom_config" && integration.customConfig && (
<>
<p className="text-xs text-emerald-500">
{t('ai.codex.customConfigHint').replace(
'{provider}',
integration.customConfig.displayName || integration.customConfig.providerName,
)}
</p>
{integration.customConfig.envKey && !integration.customConfig.envKeyPresent && !integration.customConfig.hasHardcodedApiKey && (
<p className="text-xs text-amber-500">
{t('ai.codex.customConfigMissingEnvKey').replace(
'{envKey}',
integration.customConfig.envKey,
)}
</p>
)}
</>
)}
</>
)}
{error && (
<p className="text-xs text-destructive">
{error}
</p>
)}
{found && outputText && (
<pre className="rounded-md border border-border/60 bg-background px-3 py-2 text-[11px] leading-5 text-muted-foreground whitespace-pre-wrap max-h-40 overflow-auto">
{outputText}
</pre>
)}
</div>
);
};