// @vitest-environment jsdom
import { useState } from "react";
import { createRoot, type Root } from "react-dom/client";
import { flushSync } from "react-dom";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import type { Agent, Environment, UserSecretDefinition } from "@paperclipai/shared";
import { getEnvironmentCapabilities } from "@paperclipai/shared";
import { TooltipProvider } from "@/components/ui/tooltip";
import { ToastProvider } from "../context/ToastContext";
import { AgentConfigForm, AdapterLoginPanel, ModelDropdown, subtractPersistedOverlay, type AdapterLoginDescriptor } from "./AgentConfigForm";
import { AgentLifecycleStatus, useAgentLifecycleStatus } from "./AgentLifecycleStatus";
import { queryKeys } from "../lib/queryKeys";
import { defaultCreateValues } from "./agent-config-defaults";
import { buildNewAgentHirePayload } from "../lib/new-agent-hire-payload";
import { ApiError } from "../api/client";
import { aiConnectionsApi } from "../api/ai-connections";
import { CodexLocalConfigFields } from "../adapters/codex-local/config-fields";
import type { AdapterConfigFieldsProps } from "../adapters/types";
import { DEFAULT_CODEX_LOCAL_MODEL } from "@paperclipai/adapter-codex-local";
const mockAgentsApi = vi.hoisted(() => ({
get: vi.fn(),
adapterModels: vi.fn(),
detectModel: vi.fn(),
list: vi.fn(),
testEnvironment: vi.fn(),
startAdapterAuthLogin: vi.fn(),
getAdapterAuthLoginStatus: vi.fn(),
getActiveAdapterAuthLoginSession: vi.fn(),
cancelAdapterAuthLogin: vi.fn(),
startClaudeSetupTokenLogin: vi.fn(),
getClaudeSetupTokenLoginStatus: vi.fn(),
getActiveClaudeSetupTokenLoginSession: vi.fn(),
getClaudeSetupTokenLoginPrompt: vi.fn(),
submitClaudeSetupTokenBrowserCode: vi.fn(),
completeClaudeSetupTokenLogin: vi.fn(),
cancelClaudeSetupTokenLogin: vi.fn(),
getClaudeOAuthTokenStatus: vi.fn(),
}));
// The default resume read for a test that does not exercise resume: no active
// session for the caller.
function noActiveSession() {
return Promise.reject(
new ApiError("Adapter login session not found", 404, { error: "Adapter login session not found" }),
);
}
const mockClipboard = vi.hoisted(() => ({
copyTextToClipboard: vi.fn(),
}));
const mockEnvironmentsApi = vi.hoisted(() => ({
list: vi.fn(),
capabilities: vi.fn(),
}));
const mockInstanceSettingsApi = vi.hoisted(() => ({
get: vi.fn(),
getExperimental: vi.fn(),
getGeneral: vi.fn(),
}));
const mockSecretsApi = vi.hoisted(() => ({
list: vi.fn(),
listProposals: vi.fn(),
listUserSecretDefinitions: vi.fn(async () => [] as unknown[]),
}));
vi.mock("../api/agents", () => ({
agentsApi: mockAgentsApi,
}));
vi.mock("../api/adapters", () => ({
adaptersApi: { list: async () => [{ type: "codex_local", supportedRunners: ["legacy", "paperclip"], defaultRunner: "legacy" }] },
}));
vi.mock("../api/environments", () => ({
environmentsApi: mockEnvironmentsApi,
}));
vi.mock("../api/instanceSettings", () => ({
instanceSettingsApi: mockInstanceSettingsApi,
}));
vi.mock("../api/secrets", () => ({
secretsApi: mockSecretsApi,
}));
vi.mock("../lib/clipboard", () => ({
copyTextToClipboard: mockClipboard.copyTextToClipboard,
}));
vi.mock("../context/CompanyContext", () => ({
useCompany: () => ({
companies: [{ id: "company-1", name: "Paperclip" }],
selectedCompanyId: "company-1",
selectedCompany: { id: "company-1", name: "Paperclip" },
selectionSource: "bootstrap",
loading: false,
error: null,
setSelectedCompanyId: vi.fn(),
reloadCompanies: vi.fn(),
createCompany: vi.fn(),
}),
}));
vi.mock("../adapters", () => ({
getUIAdapter: (type: string) => ({
type,
label: type === "hermes_gateway" ? "Hermes Gateway" : "Codex",
// The stand-in also records the two gates the form resolves for every
// adapter, so a test can assert the plumbing without rendering a real
// adapter's fields.
ConfigFields: (props: AdapterConfigFieldsProps) => {
if (type === "paperclip_runner") return