import assert from 'node:assert/strict'
import { existsSync, readFileSync } from 'node:fs'
import { resolve } from 'node:path'
import test from 'node:test'
import * as React from 'react'
import { renderToStaticMarkup } from 'react-dom/server'

import { AgentRunGenerationArtifacts } from '@/features/agent/components/AgentRunGenerationArtifacts'
import { GenerationJobSummaryCard, GenerationParamAuditCard, GenerationProgressCard, GenerationTraceSummaryCard, GenerationValidationErrorCard } from '@/features/agent/components/GenerationCards'
import { parseSettingsSnapshot } from '@movscript/core/agent'
import type { AgentRun } from '@/shared/infrastructure/providerSessionClient'

function readProviderSessionClientContractSource(): string {
  return [
    readFileSync(resolve('src/shared/infrastructure/providerSessionHttpClient.ts'), 'utf8'),
    readFileSync(resolve('src/shared/infrastructure/provider-session-client/types.ts'), 'utf8'),
  ].join('\n')
}

test('generation UI contract exposes stable hooks and accessible progressbars', () => {
  const progressHtml = renderToStaticMarkup(
    <GenerationProgressCard state={{
      jobId: 42,
      status: 'running',
      stage: 'rendering',
      progress: 42,
      terminal: false,
    }} />,
  )
  const jobsHtml = renderToStaticMarkup(
    <GenerationJobSummaryCard jobs={[{
      jobId: 43,
      jobType: 'video',
      status: 'succeeded',
      stage: 'completed',
      progress: 100,
      terminal: true,
      outputResourceId: 430,
    }]} />,
  )
  const traceHtml = renderToStaticMarkup(
    <GenerationTraceSummaryCard jobs={[{
      jobId: 44,
      jobType: 'image',
      status: 'failed',
      stage: 'failed',
      terminal: true,
    }]} />,
  )
  const auditHtml = renderToStaticMarkup(
    <GenerationParamAuditCard audits={[{
      stepId: 'step_1',
      jobId: 45,
      modelConfigId: 12,
      modelContractLoaded: true,
      paramsSchemaLoaded: true,
      paramsSchemaRuleCount: 2,
      inputRequirements: {
        image: { min: 1, max: 4 },
        video: { min: 0, max: 0 },
      },
      submittedInputs: {
        image: 5,
        video: 0,
      },
      supportedParams: ['duration', 'resolution'],
      providedExtraParams: ['duration', 'resolution', 'unsupported_flag'],
      submittedExtraParams: ['duration', 'resolution'],
      droppedExtraParams: ['unsupported_flag'],
      droppedTopLevelParams: ['aspect_ratio'],
      dropReasons: {
        unsupported_flag: 'unsupported_extra_param',
        aspect_ratio: 'unsupported_top_level_param',
      },
      renamedExtraParams: {
        ratio: 'aspect_ratio',
      },
      preflightErrors: [{
        code: 'INVALID_PARAMETER_COMBINATION',
        field: 'duration',
        message: 'parameter "duration" conflicts with "frames" in the local model contract',
        allowedValues: ['5', '10'],
      }],
      inputPreflightErrors: [{
        code: 'INVALID_INPUT_COUNT',
        field: 'image',
        message: 'image generation input count is above the local model contract maximum',
        requiredMin: 1,
        allowedMax: 4,
        actualCount: 5,
      }],
      repairNote: 'Retried once with backend suggested_fix after generation parameter validation failed.',
    }]} />,
  )
  const validationErrorHtml = renderToStaticMarkup(
    <GenerationValidationErrorCard errors={[
      {
        stepId: 'step_error_1',
        code: 'UNSUPPORTED_OUTPUT_TYPE',
        field: 'output_type',
        message: 'model "Reference Image" does not support output type "video"',
        allowedValues: ['image'],
      },
      {
        stepId: 'step_error_2',
        code: 'INVALID_INPUT_COUNT',
        field: 'image',
        message: 'model "Reference Image" supports at most 4 image input(s), but 5 were provided',
        requiredMin: 1,
        allowedMax: 4,
        actualCount: 5,
      },
    ]} />,
  )

  assertIncludes(progressHtml, 'data-testid="agent-generation-progress"')
  assertIncludes(progressHtml, 'data-testid="agent-generation-progress-bar"')
  assertIncludes(progressHtml, 'role="progressbar"')
  assertIncludes(progressHtml, 'aria-valuenow="42"')
  assertIncludes(jobsHtml, 'data-testid="agent-generation-job-summary"')
  assertIncludes(jobsHtml, 'data-testid="agent-generation-job-progress-bar"')
  assertIncludes(jobsHtml, 'role="progressbar"')
  assertIncludes(jobsHtml, 'aria-valuenow="100"')
  assertIncludes(traceHtml, 'data-testid="agent-generation-trace-summary"')
  assertIncludes(auditHtml, 'data-testid="agent-generation-param-audit"')
  assertIncludes(auditHtml, 'unsupported_flag (不支持)')
  assertIncludes(auditHtml, 'aspect_ratio (模型不支持)')
  assertIncludes(auditHtml, 'ratio -&gt; aspect_ratio')
  assertIncludes(auditHtml, 'schema 2 条规则')
  assertIncludes(auditHtml, '输入需求')
  assertIncludes(auditHtml, '输入预检')
  assertIncludes(auditHtml, '图片 5 个')
  assertIncludes(auditHtml, '本地预检')
  assertIncludes(auditHtml, 'duration (INVALID_PARAMETER_COMBINATION，允许 5/10)')
  assertIncludes(auditHtml, '自动修复')
  assertIncludes(validationErrorHtml, 'data-testid="agent-generation-validation-errors"')
  assertIncludes(validationErrorHtml, 'UNSUPPORTED_OUTPUT_TYPE')
  assertIncludes(validationErrorHtml, '允许值')
  assertIncludes(validationErrorHtml, 'INVALID_INPUT_COUNT')
  assertIncludes(validationErrorHtml, '输入数量')
})

test('generated result bulk candidate control explains missing resource ids', () => {
  const source = readFileSync(resolve('src/features/agent/components/GeneratedResultCard.tsx'), 'utf8')
  const bindingSource = readFileSync(resolve('src/features/agent/domain/agentGeneratedResourceBinding.ts'), 'utf8')
  const attachmentSource = readFileSync(resolve('src/features/agent/domain/agentGeneratedResultAttachments.ts'), 'utf8')
  assertIncludes(attachmentSource, 'return attachment.generated !== undefined || attachment.id.startsWith')
  assertIncludes(source, '{generated.length} 个结果')
  assertIncludes(source, 'const hasUsableGeneratedResource = generated.some')
  assertIncludes(source, '将可用的生成资源加入素材需求、画面锚点的候选列表')
  assertIncludes(source, '这些生成结果暂未返回资源 ID，暂不能复制引用或加入候选。')
  assertIncludes(bindingSource, "{ value: 'asset_slot', label: '素材需求', slot: 'candidate', entityKind: 'assetSlots' }")
  assertIncludes(bindingSource, "{ value: 'keyframe', label: '画面锚点', slot: 'candidate', entityKind: 'keyframes' }")
  assertIncludes(source, "queryKey: ['agent-generated-candidate-targets', projectId, targetConfig.entityKind]")
  assertIncludes(source, 'isGeneratedCandidateTargetRecord(record, targetConfig.value)')
  assertIncludes(source, 'generatedKeyframeCandidatePayload(targetRecord, attachment)')
  assertIncludes(source, 'createWorkspaceKeyframeCandidate(projectId, generatedKeyframeCandidatePayload(targetRecord, attachment))')
  assertIncludes(source, 'const pendingCandidateAttachments = pendingGeneratedCandidateAttachments(candidateAttachments, attachedAttachmentIds)')
  assertIncludes(source, 'const hasCandidateAttachments = pendingCandidateAttachments.length > 0')
  assertIncludes(source, '这些生成结果暂未返回可加入候选的资源 ID')
  assertIncludes(source, "resourceId !== undefined ? `#${resourceId}` : '未返回资源 ID'")
  assertIncludes(source, 'disabled={resourceId === undefined}')
  assertIncludes(source, "resourceId === undefined ? '无资源 ID'")
  assertIncludes(source, 'data-testid="agent-generated-resource-candidate-missing-id"')
  assertIncludes(source, '该生成结果暂未返回资源 ID，不能加入候选。')
  assertIncludes(source, 'enabled: open && !!projectId && hasCandidateAttachments')
  assertIncludes(source, "disabled={!projectId || candidateAttachments.length === 0 || attachStatus === 'attaching'}")
  assertIncludes(source, 'disabled={!projectId || candidateAttachments.length === 0}')
})

test('generated result card limits initially mounted media previews', () => {
  const source = readFileSync(resolve('src/features/agent/components/GeneratedResultCard.tsx'), 'utf8')
  const generatedMediaPreviewCss = readFileSync(resolve('../../packages/ui/src/components/business/agent/generated/media-preview/styles.css'), 'utf8')
  const panelThreadMessageCss = readFileSync(resolve('../../packages/ui/src/components/business/agent/panel/thread-message/styles.css'), 'utf8')

  assertIncludes(source, 'AGENT_GENERATED_RESULT_INITIAL_RENDER_LIMIT = 4')
  assertIncludes(source, 'const [expandedResults, setExpandedResults] = useState(false)')
  assertIncludes(source, 'const renderedGenerated = expandedResults ? generated : generated.slice(0, AGENT_GENERATED_RESULT_INITIAL_RENDER_LIMIT)')
  assertIncludes(source, 'const hiddenGeneratedCount = generated.length - renderedGenerated.length')
  assertIncludes(source, 'renderedGenerated.map')
  assertIncludes(source, 'setExpandedResults(true)')
  assertIncludes(source, 'lightweightVideoThumb')
  assertIncludes(source, 'surface="dark"')
  assertIncludes(generatedMediaPreviewCss, '.ms-agent-generated-media-preview > .ms-button__content > .resource-media-thumb')
  assertIncludes(panelThreadMessageCss, 'width: calc(100% - 20px)')
  assertIncludes(panelThreadMessageCss, 'margin-left: 10px')
  assertIncludes(panelThreadMessageCss, '.ai-agent-panel-shell .ms-agent-message--assistant .ms-agent-generated-media-preview > .ms-button__content > .resource-media-thumb')
  assertNotIncludes(source, 'generated.map((attachment)')
  assertNotIncludes(source, '<AgentGeneratedMediaPreview data-testid="agent-generated-media-preview" surface="dark">')
})

test('agent console architecture separates model providers, agents, plugins, and workspace root', () => {
  const routesSource = readFileSync(resolve('src/routes/projectRoutes.ts'), 'utf8')
  const routeLayoutRegistrySource = readFileSync(resolve('src/routes/routeLayoutRegistry.ts'), 'utf8')
  const appSource = readFileSync(resolve('src/App.tsx'), 'utf8')
  const navSource = readFileSync(resolve('src/features/agent/components/AgentConsoleNav.tsx'), 'utf8')
  const consoleSource = readFileSync(resolve('src/features/agent/components/AgentConsolePage.tsx'), 'utf8')
  const pluginsPageSource = readFileSync(resolve('src/features/plugins/components/ClientPluginsPage.tsx'), 'utf8')
  const controlCenterSource = readFileSync(resolve('src/features/agent/presentation/useAgentControlCenter.ts'), 'utf8')
  const pluginToolSource = readFileSync(resolve('src/features/plugins/components/PluginToolPage.tsx'), 'utf8')
  const modelProvidersSource = readFileSync(resolve('src/features/agent/components/ModelProvidersPage.tsx'), 'utf8')
  const agentsSource = readFileSync(resolve('src/features/agent/components/AgentsPage.tsx'), 'utf8')
  const defaultAgentProviderSource = readFileSync(resolve('src/features/agent/application/defaultAgentProvider.ts'), 'utf8')
  const coreDefaultProviderSource = readFileSync(resolve('../../packages/core/src/agent/defaultProvider.ts'), 'utf8')
  const appServerChatShellSource = readFileSync(resolve('src/features/agent/components/AppServerChatShell.tsx'), 'utf8')
  const chatDataSourceFactorySource = readFileSync(resolve('src/features/agent/application/agentChatDataSourceFactory.ts'), 'utf8')
  const providerRoutesSource = readFileSync(resolve('src/features/agent/application/providerRoutes.ts'), 'utf8')
  const packageJson = JSON.parse(readFileSync(resolve('package.json'), 'utf8')) as { scripts?: Record<string, string> }
  const electronApiSource = readFileSync(resolve('src/shared/contracts/electronApi.ts'), 'utf8')
  const workspaceConfigIpcSource = readFileSync(resolve('../../apps/frontend/electron/ipc/movscriptWorkspaceConfigIpc.ts'), 'utf8')
  const workspacePreloadSource = readFileSync(resolve('../../apps/frontend/electron/preload/api/movscriptWorkspace.ts'), 'utf8')
  const workspaceConfigSource = readFileSync(resolve('../../packages/core/src/workspace/config.ts'), 'utf8')
  const workspaceConfigNodeSource = readFileSync(resolve('../../packages/core/src/workspace/node/config.ts'), 'utf8')
  const uiBusinessIndexSource = readFileSync(resolve('../../packages/ui/src/components/business/index.ts'), 'utf8')
  const appSettingsSource = readFileSync(resolve('src/shared/contracts/appSettings.ts'), 'utf8')
  const coreAppSettingsSource = readFileSync(resolve('../../packages/core/src/shared/appSettings.ts'), 'utf8')
  const appSettingsStoreSource = readFileSync(resolve('src/shared/infrastructure/appSettingsStore.ts'), 'utf8')
  const appSettingsPageSource = readFileSync(resolve('src/features/settings/components/AppSettingsPage.tsx'), 'utf8')
  const settingsIpcSource = readFileSync(resolve('../../apps/frontend/electron/ipc/settingsIpc.ts'), 'utf8')
  const workspaceDefaultsSource = readFileSync(resolve('../../apps/frontend/electron/services/movscriptWorkspaceDefaults.ts'), 'utf8')
  const zh = readFileSync(resolve('src/i18n/locales/zh-CN.json'), 'utf8')
  const en = readFileSync(resolve('src/i18n/locales/en-US.json'), 'utf8')

  assertIncludes(routesSource, "plugins: '/plugins'")
  assertNotIncludes(routesSource, ['legacy', 'Agent', 'Plugins'].join(''))
  assertNotIncludes(routesSource, "'/agent/plugins'")
  assertNotIncludes(routesSource, "agentsMovscript: '/agents/movscript'")
  assertIncludes(routesSource, "agentProvider: '/agents/:providerRouteKey'")
  assertNotIncludes(routesSource, "agentsCodex: '/agents/codex'")
  assertNotIncludes(routesSource, "agentsMova: '/agents/mova'")
  assertIncludes(providerRoutesSource, 'function providerTitle')
  assertNotIncludes(providerRoutesSource, "key === 'mova'")
  assertNotIncludes(providerRoutesSource, "key === 'codex'")
  assertNotIncludes(providerRoutesSource, "key === 'claude'")
  assertIncludes(routesSource, "modelProviders: '/model-providers'")
  assertIncludes(routesSource, "workspaceConfig: '/workspace/config'")
  assertIncludes(routesSource, "workspaceReview: '/workspace/review'")
  assertIncludes(appSource, '<Route path={ROUTES.plugins} element={<ClientPluginsPage />} />')
  assertIncludes(routeLayoutRegistrySource, "routeId: 'plugins'")
  assertIncludes(routeLayoutRegistrySource, 'pathnamePattern: ROUTES.plugins')
  assertIncludes(routeLayoutRegistrySource, '...DETAIL_DOCUMENT_ROUTE')
  assertNotIncludes(appSource, `ROUTES.${['legacy', 'Agent', 'Plugins'].join('')}`)
  assertIncludes(appSource, '<Route path={ROUTES.modelProviders} element={<ModelProvidersPage />} />')
  assertNotIncludes(appSource, '<Route path={ROUTES.agentsMovscript} element={<AgentsPage />} />')
  assertIncludes(appSource, 'function AgentsRedirect()')
  assertIncludes(appSource, 'providerRoute(appServerProvider)')
  assertIncludes(appSource, '<Route path={ROUTES.agents} element={<AgentsRedirect />} />')
  assertIncludes(appSource, '<Route path={ROUTES.agentProvider} element={<AgentsPage />} />')
  assertNotIncludes(appSource, '<Route path={ROUTES.agentsCodex} element={<AgentsPage />} />')
  assertNotIncludes(appSource, '<Route path={ROUTES.agentsMova} element={<AgentsPage />} />')
  assertIncludes(appSource, '<Route path={ROUTES.workspaceConfig} element={<MovScriptWorkspaceFilesPage />} />')

  assertIncludes(navSource, "label: 'Overview'")
  assertIncludes(navSource, "label: 'Model Providers'")
  assertIncludes(navSource, "label: 'Agents'")
  assertIncludes(navSource, "label: 'Plugins'")
  assertIncludes(navSource, "label: 'Workspace'")
  assertIncludes(navSource, "match: [ROUTES.workspaceConfig, ROUTES.workspaceReview]")
  assert.equal(existsSync(resolve('../../packages/ui/src/components/business/agent/chat/list')), false)
  assertNotIncludes(readFileSync(resolve('../../packages/ui/src/components/business/index.ts'), 'utf8'), 'AgentConversationListPanel')
  assertIncludes(pluginsPageSource, '<AgentPageShell data-testid="client-plugins-page">')
  assertIncludes(pluginsPageSource, '<AgentConsoleHeader>')
  assertIncludes(pluginsPageSource, '<AgentConsoleNav compact />')
  assertIncludes(pluginsPageSource, '<PluginPageShellBody>')
  assertNotIncludes(pluginsPageSource, 'className="plugin-page-layout"')
  assertNotIncludes(pluginsPageSource, '<PluginPageLayout>')
  assertIncludes(pluginToolSource, "t('plugins.toolDisabledDescription')")
  assertNotIncludes(pluginToolSource, 'agent plugin files')
  assertIncludes(en, 'provider plugin files')
  assertIncludes(en, 'Checking plugin sources for each enabled provider')
  assertIncludes(en, 'Plugin execution and embedding are handled by the active provider.')
  assertNotIncludes(en, 'agent plugin files')
  assertNotIncludes(en, 'Checking plugin sources for each enabled agent')
  assertIncludes(en, '"selectNewConversationProvider": "Select the provider for new conversations"')
  assertNotIncludes(en, 'selectNewConversationAgent')
  assertIncludes(zh, 'provider 插件文件')
  assertIncludes(zh, '正在检查每个已启用 provider 的插件来源')
  assertIncludes(zh, '插件执行和嵌入由当前 provider 处理')
  assertNotIncludes(zh, 'agent 插件文件')
  assertNotIncludes(zh, '已启用 Agent 的插件来源')
  assertIncludes(zh, '"selectNewConversationProvider": "选择新建会话使用的 provider"')
  assertNotIncludes(zh, 'selectNewConversationAgent')

  assertIncludes(modelProvidersSource, "queryFn: () => fetchAgentBackendModels()")
  assertIncludes(modelProvidersSource, 'title="Backend Providers"')
  assertIncludes(modelProvidersSource, 'title="Local Providers"')
  assertIncludes(modelProvidersSource, 'function groupBackendModelProviders')
  assertIncludes(modelProvidersSource, "provider profile config / modelProviders")
  assertIncludes(modelProvidersSource, 'await providerSessionClient.saveWorkspaceConfig({ modelProviders: providers.map(modelProviderToConfigRecord) })')
  assertNotIncludes(modelProvidersSource, ['Agent', 'Profile', 'Model', 'Provider'].join(''))
  assertNotIncludes(modelProvidersSource, ['get', 'Agent', 'Profile', 'Config'].join(''))
  assertNotIncludes(modelProvidersSource, ['save', 'Agent', 'Profile', 'Config'].join(''))
  assertIncludes(modelProvidersSource, 'return providers')
  assertNotIncludes(modelProvidersSource, 'return providers.length > 0 ? providers : [DEFAULT_PROVIDER]')

  assertIncludes(agentsSource, "queryFn: () => fetchAgentBackendModels()")
  assertIncludes(agentsSource, 'function ProviderSelect')
  assertIncludes(agentsSource, 'const appServerProviders = useMemo(() => providers.filter(usesAppServerProtocol), [providers])')
  assertIncludes(agentsSource, 'appServerProviders.map((provider) =>')
  assertIncludes(agentsSource, 'function providerRoute(providerKey: string)')
  assertIncludes(agentsSource, 'function fallbackAppServerProvider(providerKey: string): ProviderConfig')
  assertIncludes(agentsSource, "queryKey: ['agents-workspace-config', 'default']")
  assertIncludes(agentsSource, 'new ProviderSessionClient(undefined, { providerProfileKey: activeAppServerKey })')
  assertIncludes(agentsSource, "queryKey: ['agents-workspace-config', activeAppServerKey]")
  assertIncludes(agentsSource, 'buildProviderOptions(defaultWorkspaceConfigQuery.data, backendModelsQuery.data ?? [])')
  assertNotIncludes(agentsSource, "type AgentTab = 'codex' | 'mova'")
  assertNotIncludes(agentsSource, "type AgentConfigKey = 'codex' | 'mova'")
  assertIncludes(agentsSource, 'const configLocked = running')
  assertIncludes(agentsSource, '{title} 运行中：停止 app-server 后才能修改 provider 和账号来源。')
  assertNotIncludes(agentsSource, ['await window.api.', 'stop', 'Agent', 'Runtime', '()'].join(''))
  assertNotIncludes(agentsSource, ['ensure', 'Agent', 'Runtime'].join(''))
  assertNotIncludes(agentsSource, 'async function restartRuntime()')
  assertIncludes(agentsSource, 'async function restartAppServer()')
  assertIncludes(agentsSource, "重启中...' : '重启'")
  assertNotIncludes(agentsSource, ['build', 'MovScript', 'Agent', 'Record'].join(''))
  assertIncludes(agentsSource, 'buildAppServerRecord(draft, providerOption, resolved.enabled, resolved.appServerProfile)')
  assertIncludes(agentsSource, 'enabled,')
  assertIncludes(agentsSource, "type AppServerAuthSource = 'model-provider' | 'local-home' | 'managed-home' | 'custom-config' | 'none'")
  assertIncludes(agentsSource, "label=\"Provider\"")
  assertIncludes(agentsSource, "value={`provider:${option.id}`}")
  assertNotIncludes(agentsSource, 'label="Auth Source"')
  assertNotIncludes(agentsSource, 'label="Home"')
  assertNotIncludes(agentsSource, 'label="Workspace Dir"')
  assertNotIncludes(agentsSource, '可执行文件')
  assertNotIncludes(agentsSource, 'label="显示名称"')
  assertNotIncludes(agentsSource, 'type AgentAuthSource')
  assertIncludes(agentsSource, 'PROVIDER_LOCAL_HOME_COMPAT_MODE')
  assertNotIncludes(agentsSource, 'LEGACY_PROVIDER_LOCAL_HOME_MODE')
  assertNotIncludes(agentsSource, 'OLD_LOCAL_HOME_CONFIG_MODE')
  assertNotIncludes(agentsSource, "await saveProviderConfig('movscript'")
  assertIncludes(agentsSource, 'await saveProviderConfig(providerSessionClient, providerKey')
  assertIncludes(agentsSource, "config: { mode: 'local-home' }")
  assertNotIncludes(agentsSource, `config: { mode: '${['local', 'Codex'].join('')}' }`)
  assertNotIncludes(agentsSource, "localHome: '~/.codex'")
  assertIncludes(agentsSource, "config: { mode: 'customApiKey' }")
  assertIncludes(agentsSource, "config: { mode: 'customConfig' }")
  assertIncludes(agentsSource, "config: { mode: 'none' }")
  assertIncludes(agentsSource, "config: { mode: 'backendKey', modelProviderRef: draft.providerRef }")
  assertIncludes(agentsSource, "delete currentProvider.providerRef")
  assertIncludes(agentsSource, "delete currentProvider.baseUrl")
  assertIncludes(agentsSource, "delete currentProvider.home")
  assertIncludes(agentsSource, "delete currentProvider.workspaceDir")
  assertIncludes(agentsSource, 'providers: {')
  assertIncludes(defaultAgentProviderSource, 'ensureDefaultAgentProviderFromBackend')
  assertIncludes(defaultAgentProviderSource, 'resolveDefaultAgentProviderFromBackend')
  assertIncludes(coreDefaultProviderSource, "defaultAgentProvider: {")
  assertIncludes(coreDefaultProviderSource, "source: 'backend-model'")
  assertIncludes(coreDefaultProviderSource, "config: {")
  assertIncludes(coreDefaultProviderSource, "mode: 'backendKey'")
  assertIncludes(coreDefaultProviderSource, "authSource: 'model-provider'")
  assertIncludes(appServerChatShellSource, 'ensureDefaultAgentProviderFromBackend({ provider, ...(textModels.length > 0 ? { models: textModels } : {}) })')
  assertIncludes(chatDataSourceFactorySource, 'await ensureDefaultAgentProviderFromBackend({ provider, ...(textModels.length > 0 ? { models: textModels } : {}) })')
  assertIncludes(controlCenterSource, 'await ensureDefaultAgentProviderFromBackend({ provider: appServerProvider })')
  assert.equal(packageJson.scripts?.build, 'electron-vite build')
  const removedLegacyBuildScript = ['build', 'legacy', 'agent'].join(':')
  const removedLegacyBuildWrapperScript = ['build', 'with', 'legacy', 'agent'].join(':')
  assert.equal(packageJson.scripts?.[removedLegacyBuildScript], undefined)
  assert.equal(packageJson.scripts?.[removedLegacyBuildWrapperScript], undefined)
  assert.equal(packageJson.scripts?.dev, 'pnpm --filter @movscript/desktop^... --if-present build && pnpm --filter @movscript/cli build && node scripts/dev-workspace.mjs')

  assertIncludes(consoleSource, 'title="Agent Control Matrix"')
  assertIncludes(consoleSource, '控制台是 Provider 生命周期入口')
  assertNotIncludes(consoleSource, '控制台是 Agent 生命周期入口')
  assertNotIncludes(consoleSource, '刷新 Agent 状态')
  assertNotIncludes(controlCenterSource, ['await window.api.', 'stop', 'Agent', 'Runtime', '()'].join(''))
  assertNotIncludes(controlCenterSource, ['ensure', 'Agent', 'Runtime'].join(''))
  assertNotIncludes(controlCenterSource, ['async function restart', 'MovScript', 'Agent', '()'].join(''))
  assertIncludes(controlCenterSource, 'async function restartAppServer()')
  assertNotIncludes(consoleSource, 'ROUTES.agentsMovscript')
  assertNotIncludes(consoleSource, 'ROUTES.agentsCodex')
  assertNotIncludes(consoleSource, 'to={ROUTES.agentsMova}')
  assertIncludes(consoleSource, 'appServerProvidersForManagement.map((provider) =>')
  assertIncludes(consoleSource, 'to={providerRoute(provider)}')
  assertNotIncludes(consoleSource, "provider.providerKind !== 'codex'")
  assertIncludes(controlCenterSource, 'appServerProvider,')
  assertNotIncludes(consoleSource, 'function AgentProvidersPanel')
  assertNotIncludes(consoleSource, 'title="Agent 提供方"')

  assert.equal(existsSync(resolve('../../apps/frontend/electron/ipc/' + ['agent', 'Runtime', 'Ipc.ts'].join(''))), false)
  assert.equal(existsSync(resolve('../../apps/frontend/electron/preload/api/' + ['agent', 'Runtime', '.ts'].join(''))), false)
  assertNotIncludes(electronApiSource, ['stop', 'Agent', 'Runtime', '?: () => Promise<{ ok: true }>'].join(''))
  assertIncludes(workspaceConfigIpcSource, "ipcMain.handle('movscript:workspace-config-get'")
  assertIncludes(workspaceConfigIpcSource, 'providerProfileKey')
  assertIncludes(workspaceConfigIpcSource, 'readWorkspaceConfig')
  assertIncludes(workspaceConfigIpcSource, 'saveWorkspaceConfig')
  assertNotIncludes(workspaceConfigIpcSource, `ipcMain.handle('movscript:${['agent', 'profile', 'config', 'get'].join('-')}'`)
  assertNotIncludes(workspaceConfigIpcSource, `ipcMain.handle('movscript:${['agent', 'profile', 'config', 'save'].join('-')}'`)
  assertNotIncludes(workspaceConfigIpcSource, ['agent', 'Profile', 'Dir', 'Name'].join(''))
  assertNotIncludes(workspaceConfigIpcSource, ['read', 'Agent', 'Profile', 'Config'].join(''))
  assertNotIncludes(workspaceConfigIpcSource, ['save', 'Agent', 'Profile', 'Config'].join(''))
  assertIncludes(workspaceConfigIpcSource, "applyNullableField(next, 'modelProviders', input.modelProviders)")
  assertIncludes(workspaceConfigIpcSource, "applyNullableField(next, 'providers', input.providers)")
  assertIncludes(electronApiSource, 'export type ElectronMovScriptWorkspaceConfig = {')
  assertIncludes(electronApiSource, 'export type ElectronMovScriptWorkspaceConfigSaveInput = {')
  assertIncludes(electronApiSource, 'providerProfileKey?: string')
  assertIncludes(electronApiSource, 'getMovScriptWorkspaceRoot?: (input?: { workspaceDir?: string }) => Promise<ElectronMovScriptWorkspaceRootResult>')
  assertIncludes(electronApiSource, 'getMovScriptWorkspaceConfig?: (input?: { workspaceDir?: string; providerProfileKey?: string }) => Promise<ElectronMovScriptWorkspaceConfig>')
  assertIncludes(electronApiSource, 'listProviderSessions?: (input?: { workspaceDir?: string; providerProfileKey?: string }) => Promise<{ sessions: ElectronProviderSessionSummary[] }>')
  assertNotIncludes(electronApiSource, ['Electron', 'MovScript', 'Agent', 'Profile', 'Config'].join(''))
  assertNotIncludes(electronApiSource, ['get', 'MovScript', 'Agent', 'Profile', 'Config'].join(''))
  assertNotIncludes(electronApiSource, ['save', 'MovScript', 'Agent', 'Profile', 'Config'].join(''))
  assertNotIncludes(electronApiSource, ['agent', 'Profile', 'Dir', 'Name'].join(''))
  assertIncludes(workspacePreloadSource, "getMovScriptWorkspaceRoot: (input) => ipcRenderer.invoke('movscript:workspace-root-get', input)")
  assertIncludes(workspacePreloadSource, "getMovScriptWorkspaceConfig: (input) => ipcRenderer.invoke('movscript:workspace-config-get', input)")
  assertIncludes(workspacePreloadSource, "listProviderSessions: (input) => ipcRenderer.invoke('movscript:provider-sessions-list', input)")
  assertNotIncludes(workspacePreloadSource, ['get', 'MovScript', 'Agent', 'Profile', 'Config'].join(''))
  assertNotIncludes(workspacePreloadSource, ['agent', 'profile', 'config', 'get'].join('-'))
  assertIncludes(workspaceConfigSource, "export const MOVSCRIPT_WORKSPACE_CONFIG_SCHEMA = 'movscript.workspace-config.v2'")
  assertNotIncludes(workspaceConfigSource, 'MOVSCRIPT_LEGACY_PROFILE_CONFIG_SCHEMA')
  assertNotIncludes(workspaceConfigSource, ['MOVSCRIPT', 'LEGACY', 'AGENT', 'PROFILE', 'CONFIG', 'SCHEMA'].join('_'))
  assertIncludes(workspaceConfigSource, 'export interface MovScriptWorkspaceConfig')
  assertIncludes(workspaceConfigNodeSource, 'export function resolveMovScriptWorkspacePaths')
  assertIncludes(workspaceConfigNodeSource, 'export function ensureMovScriptWorkspace')
  assertIncludes(workspaceConfigNodeSource, 'export function readMovScriptWorkspaceConfig')
  assertIncludes(workspaceConfigNodeSource, 'export function writeMovScriptWorkspaceConfig')
  assertNotIncludes(workspaceConfigSource, ['export type MovScript', 'Agent', 'Profile', 'Config = MovScriptWorkspaceConfig'].join(''))
  assertNotIncludes(workspaceConfigSource, ['export const readMovScript', 'Agent', 'Profile', 'Config = readMovScriptWorkspaceConfig'].join(''))
  assertIncludes(workspaceConfigSource, 'modelProviders?: Array<Record<string, unknown>>')
  assertIncludes(workspaceConfigSource, 'providers?: Record<string, Record<string, unknown>>')
  assertIncludes(uiBusinessIndexSource, 'AgentArtifactsPageBody')
  assertIncludes(uiBusinessIndexSource, 'AgentArtifactDetailCard')
  assertIncludes(uiBusinessIndexSource, 'AgentArtifactMetaItem')
  assertIncludes(uiBusinessIndexSource, 'ReviewWorkspaceArtifactList')
  assertIncludes(uiBusinessIndexSource, 'ReviewWorkspaceArtifactPanel')
  assertIncludes(uiBusinessIndexSource, 'ReviewWorkspaceWorkspaceList')
  assertIncludes(uiBusinessIndexSource, 'ReviewWorkspaceWorkspacePanel')
  assertIncludes(appSettingsSource, 'AppSettings')
  assertIncludes(appSettingsSource, "from '@movscript/core/shared'")
  assertIncludes(coreAppSettingsSource, 'movScriptWorkspaceDir?: string')
  assertIncludes(appSettingsStoreSource, 'setMovScriptWorkspaceDir')
  assertIncludes(appSettingsPageSource, "title={t('appSettings.movScriptWorkspaceTitle')}")
  assertIncludes(settingsIpcSource, 'setDesktopDefaultMovScriptWorkspaceDir(settings?.movScriptWorkspaceDir)')
  assertIncludes(workspaceDefaultsSource, 'configuredDesktopMovScriptWorkspaceDir')
})

test('provider session workspaces are named as workspace artifacts, not MovScript file workspaces', () => {
  const clientSource = readProviderSessionClientContractSource()
  const workspaceKindSource = readFileSync(resolve('src/shared/contracts/movscriptWorkspace.ts'), 'utf8')

  assertIncludes(workspaceKindSource, 'export type MovScriptWorkspaceKind')
  assertNotIncludes(workspaceKindSource, 'AgentWorkspaceKind')
  assertIncludes(clientSource, 'export type { MovScriptWorkspaceKind }')
  assertNotIncludes(clientSource, 'export type AgentWorkspaceKind = MovScriptWorkspaceKind')
  assertIncludes(clientSource, 'export interface WorkspaceArtifact')
  assertIncludes(clientSource, "export type WorkspaceArtifactStatus = 'workspace' | 'accepted' | 'rejected' | 'applied' | 'superseded'")
  assertIncludes(clientSource, 'listWorkspaceArtifacts(query:')
  assertIncludes(clientSource, 'getWorkspaceArtifact(workspaceId: string)')
  assertIncludes(clientSource, 'createWorkspaceArtifact(input:')
  assertIncludes(clientSource, 'updateWorkspaceArtifact(workspaceId: string')
  assertIncludes(clientSource, 'previewApplyWorkspaceArtifact(workspaceId: string')
  assertIncludes(clientSource, 'applyWorkspaceArtifact(workspaceId: string')
  assertIncludes(clientSource, 'rejectWorkspaceArtifact(workspaceId: string')
  assertNotIncludes(clientSource, 'listWorkspaces(query:')
  assertNotIncludes(clientSource, 'getWorkspace(workspaceId: string)')
  assertNotIncludes(clientSource, 'createWorkspace(input:')
  assertNotIncludes(clientSource, 'updateWorkspace(workspaceId: string')
  assertNotIncludes(clientSource, 'previewApplyWorkspace(workspaceId: string')
  assertNotIncludes(clientSource, 'applyWorkspace(workspaceId: string')
  assertNotIncludes(clientSource, 'rejectWorkspace(workspaceId: string')
  assertNotIncludes(clientSource, 'export type AgentWorkspaceStatus = WorkspaceArtifactStatus')
  assertNotIncludes(clientSource, 'export type AgentWorkspace = WorkspaceArtifact')
  assertNotIncludes(clientSource, 'export type AgentWorkspaceApplyReview = WorkspaceArtifactApplyReview')
  assertNotIncludes(clientSource, 'export type AgentWorkspaceApplyPreview = WorkspaceArtifactApplyPreview')
})

test('agent settings exposes provider model API mode controls', () => {
  const source = readFileSync(resolve('src/features/agent/components/AIAgentSettingsPage.tsx'), 'utf8')
  const consoleSource = readFileSync(resolve('src/features/agent/components/AgentConsolePage.tsx'), 'utf8')
  const consoleNavSource = readFileSync(resolve('src/features/agent/components/AgentConsoleNav.tsx'), 'utf8')
  const coreSnapshotSource = readFileSync(resolve('../../packages/core/src/agent/settingsSnapshot.ts'), 'utf8')
  const clientSource = readProviderSessionClientContractSource()
  const sendCompletionSource = readFileSync(resolve('src/features/agent/application/agentSendCompletion.ts'), 'utf8')
  const sendStreamSource = readFileSync(resolve('src/features/agent/application/agentSendStream.ts'), 'utf8')
  const runInteractionActionsSource = readFileSync(resolve('src/features/agent/application/agentRunInteractionActions.ts'), 'utf8')
  const settingsCss = readFileSync(resolve('../../packages/ui/src/components/business/agent/settings/styles.css'), 'utf8')
  const zh = readFileSync(resolve('src/i18n/locales/zh-CN.json'), 'utf8')
  const en = readFileSync(resolve('src/i18n/locales/en-US.json'), 'utf8')

  assertIncludes(source, "const DEFAULT_API_KIND: ProviderModelAPIKind = 'openai_responses'")
  assert.equal(existsSync(resolve('src/features/agent/application/providerCatalogRun.ts')), false)
  assertNotIncludes(sendCompletionSource, 'runTouchesProviderCatalog')
  assertNotIncludes(sendStreamSource, 'runTouchesProviderCatalog')
  assertNotIncludes(runInteractionActionsSource, 'runTouchesProviderCatalog')
  assertIncludes(zh, '"description": "管理 provider 配置文件、已安装能力、Skills、工具和运行记录。"')
  assertIncludes(en, '"description": "Manage provider config files, installed capabilities, Skills, tools, and run history."')
  assertIncludes(zh, '"description": "管理 provider 配置文件、已安装能力、Skills、工具权限、模型、运行限制和配置快照。"')
  assertIncludes(en, '"description": "Manage provider config files, installed capabilities, Skills, tool permissions, model, provider-session limits, and settings snapshots."')
  assertNotIncludes(zh, '管理本地 Agent')
  assertNotIncludes(en, 'Manage persistent local Agent')
  assertNotIncludes(zh, '个人 AI 助手')
  assertNotIncludes(en, 'personal AI assistants')
  assertIncludes(zh, '"providerSessionLimits": "Provider 会话限制"')
  assertIncludes(en, '"runtimeLimits": "Provider session limits"')
  assertNotIncludes(zh, '"policy": "运行限制"')
  assertNotIncludes(en, '"policy": "Runtime limits"')
  assertNotIncludes(zh, '"policy": "权限与限制"')
  assertNotIncludes(en, '"policy": "Permissions and limits"')
  assertIncludes(source, "'openai_responses'")
  assertIncludes(source, "'openai_chat_completions'")
  assertIncludes(source, "'anthropic_messages'")
  assertIncludes(source, "apiKind: selectedApiKind")
  assertIncludes(source, 'baseURL: baseURLValue')
  assertIncludes(source, "modelConfigId: selectedModel.id")
  assertNotIncludes(source, 'function buildConfigFileModelFromWorkspace')
  assertNotIncludes(source, 'await saveCurrentConfigFileModel(buildConfigFileModelFromWorkspace')
  assertNotIncludes(source, 'function buildConfigFileModelFromSnapshotModel')
  assertNotIncludes(source, 'model: buildConfigFileModelFromSnapshotModel(snapshot.model)')
  assertIncludes(source, 'const configFileWrites = new Map<string, ProviderCatalogConfigFile>()')
  assertIncludes(source, 'function queueConfigFileWrite')
  assertNotIncludes(source, 'settingsSnapshotNeedsCatalog = Boolean(selectedSettingsSnapshotForImport?.model')
  assertNotIncludes(source, "routes: [{ when: { task: taskRoutes }, use }]")
  assertNotIncludes(source, 'function configFileWithoutModel(configFile: ProviderCatalogConfigFile)')
  assertNotIncludes(source, 'await saveCurrentConfigFileModel(null, rollbackConfigFile)')
  assertIncludes(clientSource, "export type ProviderSessionLimitsOverride = Partial<Pick<ProviderSessionLimits, 'approvalMode' | 'sandboxMode' | 'maxToolCalls' | 'maxIterations' | 'execution'>>")
  assertIncludes(clientSource, 'providerSessionLimits?: ProviderSessionLimitsOverride')
  assertIncludes(clientSource, 'const providerLimits = providerSessionLimits ?? runtimeLimits')
  assertIncludes(clientSource, '...(providerLimits ? { runtimeLimits: providerLimits } : {})')
  assertIncludes(clientSource, 'function normalizeAgentRunPreview')
  assertIncludes(clientSource, 'const providerSessionLimits = preview.providerSessionLimits ?? preview.runtimeLimits')
  assertNotIncludes(clientSource, '...(runtimeLimits ? { runtimeLimits } : {})')
  assertNotIncludes(clientSource, 'AgentRunPolicyOverride')
  assertNotIncludes(clientSource, 'runPolicy?:')
  assertIncludes(source, 'const [clearingModelConfig, setClearingModelConfig] = useState')
  assertIncludes(source, 'const [modelConfigClearConfirming, setModelConfigClearConfirming] = useState')
  assertIncludes(source, 'async function clearModelConfig()')
  assertIncludes(source, 'if (!modelConfigClearConfirming)')
  assertIncludes(source, 'setModelConfigClearConfirming(true)')
  assertIncludes(source, 'setModelConfigClearConfirming(false)')
  assertIncludes(source, "'confirm-clear-model-config'")
  assertIncludes(source, "quickFix === 'confirm-clear-model-config'")
  assertIncludes(source, "scrollToSettingsSection('agent-settings-model')")
  assertIncludes(source, 'setSettingsActionFeedback(t(\'agents.settings.quickFixes.confirmClearModelConfig\'))')
  assertIncludes(source, '[baseURL, providerModelConfigValue, modelApiKey, selectedApiKind, useForChat, useForPlanner]')
  assertIncludes(source, 'settingsProviderSessionClient.clearProviderModelConfig()')
  assertNotIncludes(source, ['settingsProviderSessionClient.clear', 'Agent', 'Profile', 'Model', 'Config()'].join(''))
  assertIncludes(source, "action: 'model_cleared'")
  assertIncludes(source, 'agents.settings.auditSummaries.modelCleared')
  assertIncludes(source, 'agents.settings.clearModelConfig')
  assertIncludes(source, 'agents.settings.clearModelConfigConfirm')
  assertIncludes(source, 'data-testid="agent-settings-clear-model-config"')
  assertIncludes(source, "variant={modelConfigClearConfirming ? 'solid' : 'outline'}")
  assertIncludes(source, 'const [directModelId, setDirectModelId] = useState')
  assertIncludes(source, 'const baseURLValue = baseURL.trim()')
  assertIncludes(source, 'const usesBackendCompatibleBaseURL = isBackendCompatibleBaseURL(baseURLValue)')
  assertIncludes(source, 'const usesModelCatalog = !baseURLValue || usesBackendCompatibleBaseURL')
  assertIncludes(source, 'const usesManualModelId = !usesModelCatalog')
  assertIncludes(source, 'const directModelIdValue = directModelId.trim()')
  assertIncludes(source, 'const directModelIdHasSecret = usesManualModelId && hasSensitiveTextSecret(directModelIdValue)')
  assertIncludes(source, 'const providerModelConfigValue = usesModelCatalog ? (selectedModel ? publicModelId(selectedModel) : \'\') : directModelIdValue')
  assertIncludes(source, 'const modelValueMissing = !providerModelConfigValue')
  assertIncludes(source, 'const canSaveModelConfig = Boolean(providerModelConfigValue) && !directModelIdHasSecret')
  assertIncludes(source, 'const savedDirectModelIdHasSecret = Boolean(')
  assertIncludes(source, '&& hasSensitiveTextSecret(effectiveConfig.model)')
  assertIncludes(source, 'redactAgentTraceDebugText(modelDisplayName(textModels, effectiveConfig))')
  assertIncludes(source, '{modelValueMissing && (')
  assertIncludes(source, 'data-testid="agent-settings-provider-model-id"')
  assertIncludes(source, 'data-testid="agent-settings-provider-model-id-secret-warning"')
  assertIncludes(source, 'agents.settings.modelIdSecretsBlocked')
  assertIncludes(source, 'agents.settings.providerModelIdLabel')
  assertIncludes(source, 'agents.settings.providerModelIdHelp')
  assertIncludes(source, 'id="agent-settings-model"')
  assertIncludes(source, 'agents.settings.modelPanel')
  assertIncludes(source, 'data-testid="agent-settings-provider-api-key"')
  assertIncludes(source, 'agents.settings.providerApiKeyLabel')
  assertIncludes(source, 'agents.settings.providerCredentialHelp')
  assertNotIncludes(source, "value={modelCredentialStatusLabel}")
  assertIncludes(source, "'model-credentials'")
  assertIncludes(source, 'agents.settings.readiness.modelCredentials')
  assertIncludes(source, 'agents.settings.actionItems.modelCredentialsMissing')
  assertIncludes(source, 'agents.settings.actionItemDetails.modelCredentialsMissing')
  assertIncludes(source, 'agents.settings.actionItems.modelIdSensitive')
  assertIncludes(source, 'agents.settings.actionItemDetails.modelIdSensitive')
  assertIncludes(source, 'agents.settings.readinessDetails.modelIdSensitive')
  assertIncludes(source, "quickFix: 'confirm-clear-model-config'")
  assertIncludes(source, "quickFixLabelKey: 'agents.settings.quickFixes.confirmClearModelConfig'")
  assertIncludes(source, "quickFix === 'switch-openai-responses'")
  assertIncludes(source, "setSelectedApiKind('openai_responses')")
  assertIncludes(source, "quickFixLabelKey: 'agents.settings.quickFixes.switchOpenAIResponses'")
  assertIncludes(source, 'agents.settings.quickFixes.switchedToResponses')
  assertIncludes(source, "recordSettingsQuickFix('model', 'agents.settings.quickFixes.switchOpenAIResponses', 'mode_migration')")
  assertIncludes(source, "if (kind === 'mode_migration') return 'settings_quick_fix_mode_migration'")
  assertIncludes(source, 'agents.settings.auditSummaries.quickFixApplied')
  assertIncludes(source, 'agents.settings.auditSummaries.quickFixAppliedImmediate')
  assertIncludes(source, "placeholder={t('agents.settings.providerModelIdPlaceholder')}")
  assertIncludes(zh, '"providerModelIdPlaceholder": "填写已配置的模型 ID"')
  assertIncludes(en, '"providerModelIdPlaceholder": "Enter a configured model ID"')
  assertIncludes(source, 'const API_MODE_CAPABILITY_MATRIX')
  assertIncludes(source, 'function ApiModeCapabilityMatrix')
  assertIncludes(source, 'data-testid="agent-settings-api-mode-capabilities"')
  assertIncludes(source, 'data-testid="agent-settings-api-mode-capability-item"')
  assertIncludes(source, 'agents.settings.apiModeCapabilityPanel')
  assertIncludes(source, 'agents.settings.apiModeCapabilityBadges.${mode.badge}')
  assertIncludes(source, 'agents.settings.apiModeCapabilityItems.${itemKey}.label')
  assertIncludes(source, "'agenticPrimitive'")
  assertIncludes(source, "'structuredOutputs'")
  assertIncludes(source, "'functionCalling'")
  assertIncludes(source, "'anthropicNative'")
  assertIncludes(source, 'selectedApiKind,')
  assertIncludes(source, 'function apiModeReadinessDetailKey')
  assertIncludes(source, "id: 'api-mode'")
  assertIncludes(source, "id: 'api-mode-compatibility'")
  assertIncludes(source, 'agents.settings.actionItems.apiModeCompatibility')
  assertIncludes(source, 'agents.settings.readinessDetails.apiModeResponsesRecommended')
  assertIncludes(source, 'type ApiModeSwitchPlanItem')
  assertIncludes(source, 'buildApiModeSwitchTaskGraph')
  assertIncludes(source, 'recommendedSwitchTarget')
  assertIncludes(source, "if (apiKind === 'openai_chat_completions') return 'openai_responses'")
  assertIncludes(source, 'function ApiModeSwitchPlanPanel')
  assertIncludes(source, 'data-testid="agent-settings-api-mode-switch-taskGraph"')
  assertIncludes(source, 'data-testid="agent-settings-copy-api-mode-switch-taskGraph"')
  assertIncludes(source, 'data-testid="agent-settings-api-mode-switch-taskGraph-item"')
  assertIncludes(source, 'agents.settings.apiModeSwitchPlanTitle')
  assertIncludes(source, 'model: providerModelConfigValue')
  assertIncludes(source, 'updateAgentSettings({ modelId: usesModelCatalog && selectedModel ? selectedModel.id : null })')
  assertIncludes(source, "apiKindBaseURLPlaceholder")
  assertIncludes(source, "modelRoutes = effectiveConfig?.capabilities")
  assertIncludes(source, 'buildModelRouteIssues')
  assertIncludes(source, 'modelRouteIssues.length > 0')
  assertIncludes(source, 'agents.settings.modelRouteIssues')
  assertIncludes(source, "agents.settings.settingsSnapshotPanel")
  assertIncludes(source, 'function SettingsSnapshotImpactPreview')
  assertIncludes(source, 'function buildSettingsSnapshotImpactItems')
  assertIncludes(source, 'function buildSettingsSnapshotToolPermissionOverrides')
  assertNotIncludes(source, 'manifest?.metadata?.toolPermissionOverridesByConfigFile')
  assertIncludes(source, 'const overridesByConfigFile = new Map<string, ToolGrantWorkspace[]>()')
  assertIncludes(source, 'overridesByConfigFile.set(input.currentConfigFileId, input.currentToolGrantWorkspaces)')
  assertNotIncludes(source, 'toolPermissionOverrides: currentConfigFileId ? [{ configFileId: currentConfigFileId, toolGrants: toolGrantWorkspaces }] : []')
  assertIncludes(source, 'redactAgentTraceDebugText(snapshot.model.model)')
  assertIncludes(source, "'limits' | 'skills'")
  assertIncludes(source, "selected.has('limits')")
  assertIncludes(source, 'snapshotProviderSessionLimits(snapshot)')
  assertIncludes(source, 'function cloneProviderSessionLimits')
  assertNotIncludes(source, 'snapshotRuntimeLimits(snapshot)')
  assertNotIncludes(source, 'function cloneRuntimeLimits')
  assertIncludes(source, "cloned.executionMode = value")
  assertIncludes(source, "cloned.allowForcedToolCalls = value")
  assertNotIncludes(source, 'numericLimitSummary')
  assertIncludes(source, 'agents.settings.settingsSnapshotImpactDetails.limits')
  assertIncludes(source, 'agents.settings.settingsSnapshotLimitsTargetMissing')
  assertIncludes(source, 'copySnapshotImpactSummary')
  assertIncludes(source, 'data-testid="agent-settings-snapshot-impact"')
  assertIncludes(source, "value={snapshot.model?.model ? redactAgentTraceDebugText(snapshot.model.model) : '-'}")
  assertIncludes(source, 'data-testid="agent-settings-snapshot-impact-item"')
  assertIncludes(source, 'data-testid="agent-settings-copy-snapshot-impact"')
  assertIncludes(source, 'agents.settings.settingsSnapshotImpactSummaryTitle')
  assertIncludes(source, 'agents.settings.settingsSnapshotImpactScopes.${item.scope}')
  assertIncludes(source, "scope: 'config'")
  assertNotIncludes(source, "scope: 'runtime'")
  assertIncludes(source, 'lastImportBackup')
  assertIncludes(source, 'updateAgentSettings({ lastImportBackup')
  assertIncludes(source, 'function loadSettingsImportBackup')
  assertIncludes(source, 'function copySettingsImportBackup')
  assertIncludes(source, 'function clearSettingsImportBackup')
  assertNotIncludes(source, 'data-testid="agent-settings-import-backup"')
  assertNotIncludes(source, 'data-testid="agent-settings-load-import-backup"')
  assertNotIncludes(source, 'data-testid="agent-settings-copy-import-backup"')
  assertNotIncludes(source, 'data-testid="agent-settings-clear-import-backup"')
  assertIncludes(source, 'settings_backup_cleared')
  assertIncludes(source, 'agents.settings.settingsImportDoneWithBackup')
  assertIncludes(source, 'function restoreSettingsImportBackup()')
  assertIncludes(source, 'function applySettingsSnapshotWrites')
  assertNotIncludes(source, 'data-testid="agent-settings-restore-import-backup"')
  assertIncludes(source, 'data-testid="agent-settings-scope-boundary"')
  assertNotIncludes(source, "import { Link } from 'react-router-dom'")
  assertNotIncludes(source, "import { ROUTES } from '@/routes/projectRoutes'")
  assertIncludes(source, "from '@movscript/core/agent'")
  assertIncludes(source, 'hasSensitiveTextSecret')
  assertIncludes(source, 'hasSensitiveURLSecret')
  assertIncludes(source, 'redactAgentTraceDebugText')
  assertIncludes(source, 'stripSensitiveURLSecrets')
  assertIncludes(source, 'function settingsErrorMessage')
  assertIncludes(source, 'return redactAgentTraceDebugText(error instanceof Error ? error.message : String(error))')
  assertIncludes(source, 'text={settingsErrorMessage(providerModelConfigQuery.error)}')
  assertIncludes(source, 'text={settingsErrorMessage(modelsQuery.error)}')
  assertIncludes(source, 'text={settingsErrorMessage(catalogQuery.error)}')
  assertIncludes(source, 'text={settingsErrorMessage(capabilitiesQuery.error)}')
  assertIncludes(source, '{redactAgentTraceDebugText(testResult.model)}')
  assertIncludes(source, 'testResult.content ? redactAgentTraceDebugText(testResult.content)')
  assertNotIncludes(source, 'value={redactAgentTraceDebugText(providerSessionClient.baseURL)}')
  assertNotIncludes(source, 'value={redactAgentTraceDebugText(effectiveConfig?.baseURL || apiKindBaseURLPlaceholder')
  assertNotIncludes(source, 'to={ROUTES.agentDebug}')
  assertNotIncludes(source, 'data-testid="agent-settings-open-debug"')
  assertIncludes(source, 'copySettingsStatusSummary')
  assertIncludes(source, 'settingsActionItems.flatMap')
  assertIncludes(source, 'function copyRedactedSettingsLines')
  assertIncludes(source, 'lines.map(redactAgentTraceDebugText).join')
  assertIncludes(source, 'await copyRedactedSettingsLines(lines)')
  assertIncludes(source, 'data-testid="agent-settings-copy-status"')
  assertIncludes(source, 'data-testid="agent-settings-refresh"')
  assertIncludes(source, 'agents.settings.settingsStatusSummaryTitle')
  assertIncludes(source, 'agents.settings.copySettingsStatus')
  assertNotIncludes(source, 'agents.settings.openDebug')
  assertIncludes(source, 'agents.settings.refresh')
  assertNotIncludes(source, 'agents.debug.actions.refresh')
  assertIncludes(source, 'agents.settings.scope.controlPlane')
  assertIncludes(source, 'agents.settings.scope.futureRuns')
  assertIncludes(source, 'agents.settings.scope.debugReadOnly')
  assertNotIncludes(source, '<AgentSettingsSidebar>')
  assertIncludes(source, 'function buildSettingsActionItems')
  assertIncludes(source, 'function sortSettingsActionItems')
  assertIncludes(source, 'return sortSettingsActionItems(items)')
  assertNotIncludes(source, 'function SettingsActionItemsPanel')
  assertIncludes(source, 'AgentSettingsActionItemsPanel')
  assertIncludes(source, 'AgentSettingsReadinessPanel')
  assertIncludes(source, 'copyActionItemsSummary')
  assertIncludes(source, 'function settingsSectionLabelKey')
  assertNotIncludes(source, 'agents.settings.actionItemsSummarySection')
  assertIncludes(source, 'agents.settings.actionItemsSummaryQuickFix')
  assertNotIncludes(source, 'data-testid="agent-settings-action-items"')
  assertNotIncludes(source, 'data-testid="agent-settings-action-items-counts"')
  assertNotIncludes(source, 'data-testid="agent-settings-action-item"')
  assertNotIncludes(source, 'data-testid="agent-settings-copy-action-items"')
  assertNotIncludes(source, 'data-testid="agent-settings-action-item-reasons"')
  assertNotIncludes(source, 'data-testid="agent-settings-action-jump"')
  assertNotIncludes(source, 'data-testid="agent-settings-action-quick-fix"')
  assertNotIncludes(source, 'data-testid="agent-settings-action-feedback"')
  assertNotIncludes(source, 'data-testid="agent-settings-action-persist-hint"')
  assertIncludes(source, 'persistHintKey')
  assertIncludes(source, 'applySettingsActionQuickFix')
  assertIncludes(source, 'setSettingsActionFeedback')
  assertIncludes(source, 'hasSensitiveURLSecret')
  assertIncludes(source, 'modelBaseURLHasSecret')
  assertIncludes(source, 'hasSensitiveURLSecret(baseURLValue)')
  assertIncludes(source, "const message = t('agents.settings.baseUrlSecretsBlocked')")
  assertIncludes(source, 'function stripModelBaseURLSecrets')
  assertIncludes(source, 'setSaveError(null)')
  assertIncludes(source, 'setTestError(null)')
  assertIncludes(source, 'data-testid="agent-settings-base-url-secret-warning"')
  assertIncludes(source, 'data-testid="agent-settings-strip-base-url-secrets"')
  assertIncludes(source, "stripModelBaseURLSecrets({ audit: true })")
  assertIncludes(source, "if (options?.audit) recordSettingsQuickFix('model', 'agents.settings.quickFixes.stripSensitiveBaseURLQuery', 'sensitive_cleanup')")
  assertIncludes(source, 'disabled={!canSaveModelConfig || saving || modelRouteIssues.length > 0 || modelBaseURLHasSecret}')
  assertIncludes(source, 'disabled={!canSaveModelConfig || testing || modelRouteIssues.length > 0 || modelBaseURLHasSecret}')
  assertIncludes(source, 'hasSensitiveURLSecret(input.workspaceBaseURL)')
  assertIncludes(source, 'stripSensitiveURLSecrets')
  assertIncludes(source, "'model-base-url-sensitive'")
  assertIncludes(source, "'strip-sensitive-base-url-query'")
  assertIncludes(source, 'setBaseURL(stripSensitiveURLSecrets(baseURL))')
  assertIncludes(source, 'agents.settings.actionItems.modelBaseURLSensitive')
  assertIncludes(source, 'agents.settings.actionItemDetails.modelBaseURLSensitive')
  assertIncludes(source, 'agents.settings.actionItemPersistHints.useRuntimeEnvForSecrets')
  assertIncludes(source, 'agents.settings.quickFixes.stripSensitiveBaseURLQuery')
  assertIncludes(source, 'agents.settings.quickFixes.applied')
  assertIncludes(source, 'agents.settings.actionItemPersistHints.saveAfterQuickFix')
  assertIncludes(source, "'reset-model-workspace'")
  assertIncludes(source, "'enable-chat-route'")
  assertIncludes(source, "'reset-skill-config-workspace'")
  assertIncludes(source, 'formatSettingsSkillConfigIssue')
  assertIncludes(source, 'buildToolUnavailableReasonSummary')
  assertIncludes(source, 'compactActionReasons')
  assertIncludes(source, 'agents.settings.actionItemReasons.skillDependency')
  assertIncludes(source, 'agents.settings.actionItemReasons.toolUnavailableReason')
  assertIncludes(source, 'agents.settings.actionItemDetails.skillConfigInvalid')
  assertIncludes(source, 'recordSettingsAudit')
  assertIncludes(source, 'function modelAuditSummaryValues')
  assertIncludes(source, 'apiKind')
  assertIncludes(source, 'routes')
  assertIncludes(source, 'summary: t(\'agents.settings.auditSummaries.modelSaved\', modelAuditSummaryValues())')
  assertIncludes(source, 'summary: t(\'agents.settings.auditSummaries.modelTested\', modelAuditSummaryValues())')
  assertIncludes(source, 'function recordSettingsOperationFailure')
  assertIncludes(source, "action: 'settings_operation_failed'")
  assertIncludes(source, 'redactAgentTraceDebugText(error)')
  assertIncludes(source, 'function SettingsAuditTrailPanel')
  assertIncludes(source, 'function formatSettingsAuditAction')
  assertIncludes(source, "defaultValue: action")
  assertIncludes(source, 'redactAgentTraceDebugText(entry.summary)')
  assertIncludes(source, "entry.action.endsWith('_failed')")
  assertIncludes(source, 'data-audit-status={isFailure ? \'failed\' : \'ok\'}')
  assertIncludes(source, "variant={isFailure ? 'soft' : 'outline'}")
  assertIncludes(source, 'copyAuditSummary')
  assertIncludes(source, 'data-testid="agent-settings-audit-trail"')
  assertIncludes(source, 'data-testid="agent-settings-audit-entry"')
  assertIncludes(source, 'data-testid="agent-settings-copy-audit"')
  assertIncludes(source, 'data-testid="agent-settings-clear-audit"')
  assertNotIncludes(source, "title={t('agents.settings.settingsAuditPanel')}")
  assertIncludes(source, 'agents.settings.settingsAuditSummaryTitle')
  assertIncludes(source, 'agents.settings.auditSummaries.settingsSnapshotImported')
  assertIncludes(source, 'agents.settings.auditSummaries.operationFailed')
  assertNotIncludes(source, "title={t('agents.settings.readinessPanel')}")
  assertNotIncludes(source, "title={t('agents.settings.configurationMapPanel')}")
  assertIncludes(source, 'SETTINGS_NAV_SECTIONS')
  assertIncludes(source, "providerProfileKey: 'codex'")
  assertIncludes(source, "providerProfileKey: 'mova'")
  assertIncludes(source, 'BUILT_IN_PROVIDER_PROFILE_CONFIG_FALLBACKS')
  assertIncludes(source, 'normalizeProviderSettings(settings).providers')
  assertNotIncludes(source, 'for (const profile of BUILT_IN_PROVIDER_PROFILE_CONFIGS)')
  assertIncludes(source, 'function supportsProviderSessionWorkspaceCatalog')
  assertIncludes(source, 'fallback?.supportsWorkspaceCatalogInspection ?? true')
  assertNotIncludes(source, "supportsWorkspaceCatalogInspection: profileId === 'mova'")
  assertNotIncludes(source, "provider.kind === 'mova'")
  assertNotIncludes(source, "provider.appServerProfile?.providerKey === 'mova'")
  assertNotIncludes(source, ['agent', 'Profile', 'Dir', 'Name'].join(''))
  assertNotIncludes(source, "providerProfileKey: '.codex'")
  assertIncludes(source, "const SETTINGS_NAV_SECTIONS = [\n  { id: 'agent-settings-config-files'")
  assertOrdered(source, [
    "id: 'agent-settings-config-files'",
    "id: 'agent-settings-skills'",
    "id: 'agent-settings-tools'",
    "id: 'agent-settings-model'",
    "id: 'agent-settings-snapshot'",
  ])
  assertOrdered(source, [
    'id="agent-settings-config-files"',
    'id="agent-settings-model"',
  ])
  assertNotIncludes(settingsCss, '.agent-settings-main > #agent-settings-config-files')
  assertNotIncludes(settingsCss, 'order: -70;')
  assertNotIncludes(source, 'function ConfigurationMapPanel')
  assertIncludes(source, 'scrollToSettingsSection')
  assertNotIncludes(source, 'data-testid="agent-settings-management-shortcuts"')
  assertNotIncludes(source, 'data-testid={`agent-settings-management-shortcut-${area.id}-${view}`}')
  assertNotIncludes(source, 'onLayerJump={selectManagementView}')
  assertNotIncludes(source, 'const showGeneralSettingsSections = !isFocusedManagementRoute')
  assertNotIncludes(source, 'const showSkillsManagementPanel = !isFocusedManagementRoute || focusedManagementArea === \'skills\'')
  assertNotIncludes(source, 'const showToolsManagementPanel = !isFocusedManagementRoute || focusedManagementArea === \'tools\'')
  assertNotIncludes(source, 'data-testid="agent-settings-focused-management-route"')
  assertNotIncludes(source, 'agents.settings.focusedManagementRoute')
  assertNotIncludes(source, 'agents.settings.managementShortcutsPanel')
  assertNotIncludes(source, 'agents.settings.managementShortcutsHelp')
  assertNotIncludes(source, 'location.hash')
  assertNotIncludes(source, 'decodeURIComponent(location.hash.slice(1))')
  assertIncludes(consoleSource, 'function agentSettingsSectionPath')
  assertIncludes(consoleSource, "agentSettingsSectionPath('agent-settings-skills')")
  assertIncludes(consoleSource, "agentSettingsSectionPath('agent-settings-tools')")
  assertNotIncludes(consoleSource, 'agentSettingsManagementPath')
  assertIncludes(consoleSource, '聚合 Model Providers、Agents、Plugins 和 Workspace Root 的状态')
  assertIncludes(consoleSource, 'title="Provider 数据流与能力探针"')
  assertIncludes(consoleSource, 'createAgentChatDataSourceForProvider(provider)')
  assertIncludes(consoleSource, 'probeAgentChatDataSourceCapabilities')
  assertIncludes(consoleSource, '刷新 Provider 能力')
  assertNotIncludes(consoleSource, '刷新 Agent 能力')
  assertIncludes(consoleNavSource, "label: 'Model Providers'")
  assertIncludes(consoleNavSource, "description: '本地模型供应商、Base URL、API Key'")
  assertIncludes(consoleNavSource, "label: 'Agents'")
  assertIncludes(consoleNavSource, "label: 'Plugins'")
  assertIncludes(consoleNavSource, "label: 'Workspace'")
  assertIncludes(consoleNavSource, '插件和 workspace root 是全局入口')
  assertNotIncludes(consoleNavSource, "label: '模型与能力配置'")
  assertIncludes(consoleSource, 'title="Model Providers"')
  assertIncludes(consoleSource, '管理本地模型供应商、Base URL、API Key 和默认模型路由。')
  assertIncludes(consoleSource, 'appServerProvidersForManagement.map((provider) =>')
  assertIncludes(consoleSource, 'title={provider.label}')
  assertIncludes(consoleSource, 'title="Workspace"')
  assertIncludes(consoleSource, 'title="Skills"')
  assertIncludes(consoleSource, '管理当前配置文件的 Skill 激活候选、依赖和冲突。')
  assertNotIncludes(consoleSource, '编辑 Skill instruction，管理当前配置文件启用、依赖、冲突和运行时解释。')
  assertIncludes(consoleSource, 'title="Tools"')
  assertIncludes(consoleSource, 'title="Plugins"')
  assertIncludes(consoleSource, '插件是全局扩展入口')
  assertNotIncludes(consoleSource, 'title="MovScript 运行记录"')
  assertNotIncludes(consoleSource, 'title="高级诊断"')
  assertIncludes(source, 'id="agent-settings-model"')
  assertIncludes(source, 'id="agent-settings-skills"')
  assertIncludes(source, 'buildSettingsReadinessItems')
  assertNotIncludes(source, 'function SettingsReadinessPanel')
  assertNotIncludes(source, legacyAgentTerm('agents.settings.', 'runtime', 'Limit', 'SetsPanel'))
  assertNotIncludes(source, legacyAgentTerm('active', 'Runtime', 'Limit', 'SetFromSettings'))
  assertNotIncludes(source, legacyAgentTerm('defaultAgent', 'Runtime', 'Limit', 'Sets'))
  assertNotIncludes(source, legacyAgentTerm('function ', 'Runtime', 'Limit', 'SetRow'))
  assertNotIncludes(source, legacyAgentTerm('select', 'Runtime', 'Limit', 'Set'))
  assertNotIncludes(source, legacyAgentTerm('reset', 'Runtime', 'Limit', 'Sets'))
  assertNotIncludes(source, legacyAgentTerm('update', 'Runtime', 'Limit', 'Set'))
  assertIncludes(source, "'maxToolCalls'")
  assertIncludes(source, "'maxIterations'")
  assertIncludes(source, 'configFileLimitWorkspacesFromConfigFile')
  assertIncludes(source, 'normalizeConfigFileLimitWorkspaces')
  assertIncludes(source, 'data-testid="agent-settings-config-file-limits"')
  assert.equal(existsSync(resolve('src/features/agent/domain/agentTraceDebugData.ts')), false)
  assert.equal(existsSync(resolve('src/features/agent/domain/agentSettingsSnapshot.ts')), false)
  assertIncludes(coreSnapshotSource, 'id is duplicated')
  assertIncludes(coreSnapshotSource, 'name is duplicated')
  assertIncludes(coreSnapshotSource, 'parseOptionalPositiveInteger')
  assertIncludes(coreSnapshotSource, 'parseOptionalNonEmptyString')
  assertIncludes(coreSnapshotSource, 'parseOptionalDateString')
  assertIncludes(coreSnapshotSource, 'parseOptionalString')
  assertIncludes(coreSnapshotSource, 'assertAllowedKeys')
  assertIncludes(coreSnapshotSource, 'export function buildSettingsSnapshot')
  assertIncludes(coreSnapshotSource, 'function buildSnapshotModel')
  assertIncludes(coreSnapshotSource, 'hasSensitiveTextSecret(config.model)')
  assertIncludes(coreSnapshotSource, 'stripSensitiveURLSecrets(config.baseURL)')
  assertIncludes(coreSnapshotSource, 'export function parseSettingsSnapshot')
  assertIncludes(coreSnapshotSource, 'export function validateSettingsSnapshotReferences')
  assertIncludes(coreSnapshotSource, "from './sensitiveData.js'")
  assertIncludes(coreSnapshotSource, 'hasSensitiveURLSecret(baseURL)')
  assertIncludes(coreSnapshotSource, 'hasSensitiveTextSecret(model)')
  assertIncludes(coreSnapshotSource, 'model.baseURL must not include secret URL credentials')
  assertIncludes(coreSnapshotSource, 'model.model must not include API keys, bearer tokens, or secret URL credentials')
  assertIncludes(coreSnapshotSource, "assertAllowedKeys(parsed, 'agent settings snapshot', ['schema', 'schemaVersion', 'schemaUrl', 'exportedAt', 'model'")
  assertIncludes(coreSnapshotSource, 'validateSnapshotModelReference')
  assertIncludes(coreSnapshotSource, 'validateSnapshotSkillConfigReferences')
  assertIncludes(coreSnapshotSource, 'validateSnapshotToolPermissionReferences')
  assertIncludes(source, 'buildSettingsSnapshot')
  assertIncludes(source, 'parseSettingsSnapshot')
  assertIncludes(source, 'importSettingsSnapshot')
  assertIncludes(source, 'copySettingsSnapshot')
  assertIncludes(source, 'downloadSettingsSnapshot')
  assertIncludes(source, 'MAX_SETTINGS_SNAPSHOT_BYTES')
  assertIncludes(source, 'byteLength(settingsSnapshotText) > MAX_SETTINGS_SNAPSHOT_BYTES')
  assertIncludes(source, 'file.size > MAX_SETTINGS_SNAPSHOT_BYTES')
  assertIncludes(source, 'agents.settings.settingsSnapshotTooLarge')
  assertIncludes(source, 'currentSettingsSnapshotText')
  assertIncludes(source, 'updateSettingsSnapshotText')
  assertIncludes(source, 'settingsSnapshotValidation')
  assertIncludes(source, 'parsedSettingsSnapshot')
  assertIncludes(source, 'settingsSnapshotReferenceIssues')
  assertIncludes(source, 'settingsSnapshotNeedsCapabilities')
  assertIncludes(source, 'settingsSnapshotNeedsModelCatalog')
  assertIncludes(source, 'targetSnapshotConfigFile')
  assertIncludes(source, 'buildToolPermissionsWorkspaceIssues({')
  assertIncludes(source, 'agents.settings.settingsSnapshotCapabilitiesUnavailable')
  assertIncludes(source, 'agents.settings.settingsSnapshotToolPermissionsInvalid')
  assertIncludes(source, 'settingsSnapshotCanImport')
  assertNotIncludes(source, 'disabled={settingsSnapshotImporting || !settingsSnapshotCanImport}')
  assertIncludes(source, 'function SettingsSnapshotSummary')
  assertIncludes(source, 'settingsSnapshotFileInputRef')
  assertIncludes(source, 'loadSettingsSnapshotFile')
  assertIncludes(source, 'accept="application/json,.json"')
  assertIncludes(coreSnapshotSource, "AGENT_SETTINGS_SNAPSHOT_SCHEMA = 'movscript.agent.settings.snapshot.v1'")
  assertIncludes(coreSnapshotSource, 'AGENT_SETTINGS_SNAPSHOT_SCHEMA_VERSION = 1')
  assertIncludes(coreSnapshotSource, 'AGENT_SETTINGS_SNAPSHOT_SCHEMA_URL =')
  assertIncludes(coreSnapshotSource, 'schema: AGENT_SETTINGS_SNAPSHOT_SCHEMA')
  assertIncludes(coreSnapshotSource, 'schemaVersion: AGENT_SETTINGS_SNAPSHOT_SCHEMA_VERSION')
  assertIncludes(coreSnapshotSource, 'schemaUrl: AGENT_SETTINGS_SNAPSHOT_SCHEMA_URL')
  assertIncludes(clientSource, 'ProviderModelCapabilityRoutePublic')
  assertIncludes(clientSource, 'ProviderModelCredentialStatusPublic')
  assertIncludes(clientSource, "@movscript/core/agent/protocol")
  assertIncludes(clientSource, 'ProviderSessionClientInput')
  assertIncludes(clientSource, 'ProviderSessionHealth')
  assertIncludes(clientSource, 'ProviderCatalogInspectResponse')
  assertNotIncludes(clientSource, 'AgentClientInput')
  assertNotIncludes(clientSource, 'AgentHealth')
  assertNotIncludes(clientSource, 'AgentInspectResponse')
  assertIncludes(clientSource, 'getProviderModelConfig(): Promise<ProviderModelConfigPublic>')
  assertIncludes(clientSource, 'saveProviderModelConfig(input:')
  assertIncludes(clientSource, 'clearProviderModelConfig(): Promise<ProviderModelConfigPublic>')
  assertNotIncludes(clientSource, 'getWorkspaceModelConfig')
  assertNotIncludes(clientSource, 'saveWorkspaceModelConfig')
  assertNotIncludes(clientSource, 'clearWorkspaceModelConfig')
  assertNotIncludes(clientSource, ['get', 'Agent', 'Profile', 'Model', 'Config'].join(''))
  assertNotIncludes(clientSource, ['save', 'Agent', 'Profile', 'Model', 'Config'].join(''))
  assertNotIncludes(clientSource, ['clear', 'Agent', 'Profile', 'Model', 'Config'].join(''))
  assertIncludes(clientSource, 'clearModelConfig(): Promise<ProviderModelConfigPublic>')
  assertIncludes(clientSource, "this.deleteJSON('/model-config')")
  assertIncludes(source, 'hasSensitiveTextSecret(directModelIdValue)')
  assertIncludes(source, 'hasSensitiveURLSecret(baseURLValue)')
  assertNotIncludes(source, 'PROVIDER_API_KEY_PATTERN')
  assertIncludes(zh, '"apiKindLabel": "调用模式"')
  assertIncludes(zh, '"providerModelIdLabel": "Provider 模型 ID"')
  assertIncludes(zh, '"providerModelIdHelp": "直连 OpenAI 或 Anthropic')
  assertIncludes(zh, '"providerModelPanel": "Provider 模型"')
  assertIncludes(zh, '"providerModelPanelHelp": "填写模型 ID。Base URL 留空或指向 MovScript 后端')
  assertIncludes(zh, '"modelRequired": "请先选择模型或填写 Provider 模型 ID。"')
  assertIncludes(zh, '"modelIdSecretsBlocked": "Provider 模型 ID 看起来包含 API key')
  assertIncludes(zh, '"providerApiKeyLabel": "API key"')
  assertIncludes(zh, '"providerCredentialHelp": "只有 Base URL 指向外部 Provider')
  assertIncludes(zh, '"modelCredentialStatus"')
  assertIncludes(zh, '"modelCredentials": "模型凭证"')
  assertIncludes(zh, '"modelIdSensitive": "已保存模型 ID 可能包含密钥"')
  assertIncludes(zh, '"confirmClearModelConfig": "准备清除模型配置"')
  assertIncludes(zh, '"modelIdSensitive": "已保存的直连 Provider 模型 ID 看起来包含 API key')
  assertIncludes(zh, '"modelIdSensitive": "已保存模型 ID 看起来包含密钥，导出快照时会跳过该模型配置。"')
  assertIncludes(zh, '"modelCredentialsMissing": "直连模型凭证未加载"')
  assertIncludes(zh, '"modelCredentialsReady": "API key 设置已通过 {{env}} 加载模型凭证。"')
  assertIncludes(zh, '"controlPlane": "控制面')
  assertIncludes(zh, '"futureRuns": "不展示单次运行诊断"')
  assertIncludes(zh, '"debugReadOnly": "Provider 会话观测保持只读"')
  assertIncludes(zh, '"openaiResponses": "OpenAI / Responses"')
  assertIncludes(zh, '"apiModeCapabilityPanel": "调用模式能力矩阵"')
  assertIncludes(zh, '"recommended": "推荐新 Provider Session"')
  assertIncludes(zh, '"agenticPrimitive"')
  assertNotIncludes(zh, '"legacyCompatibility"')
  assertIncludes(zh, '"apiMode": "调用模式"')
  assertIncludes(zh, '"apiModeResponsesRecommended": "OpenAI Responses 是新 provider 能力的推荐路径。"')
  assertIncludes(zh, '"apiModeCompatibility": "调用模式仍是兼容模式"')
  assertIncludes(zh, '"switchOpenAIResponses": "切到 Responses"')
  assertIncludes(zh, '"switchedToResponses": "已将本地工作区切到 OpenAI Responses')
  assertIncludes(zh, '"apiModeSwitchPlanTitle": "调用模式切换计划"')
  assertIncludes(zh, '"copyApiModeSwitchTaskGraph": "复制切换计划"')
  assertIncludes(zh, '"targetModeMigration": "{{apiKind}} 的新增 provider 工作应迁移到 {{targetApiKind}}')
  assertIncludes(zh, '"settings_quick_fix_mode_migration": "模式迁移"')
  assertIncludes(zh, '"settings_quick_fix_sensitive_cleanup": "清理敏感字段"')
  assertIncludes(zh, '"quickFixApplied": "已应用本地快速修复')
  assertIncludes(zh, '"quickFixAppliedImmediate": "已应用快速修复')
  assertIncludes(zh, '"modelRoutesPanel": "模型路由"')
  assertIncludes(zh, '"allRoutesDisabled": "至少需要启用')
  assertIncludes(zh, '"modelRouteModel": "模型"')
  assertIncludes(zh, '"settingsSnapshotPanel": "配置导入 / 导出"')
  assertIncludes(zh, '.movscript/providers/codex')
  assertIncludes(zh, '.movscript/providers/mova')
  assertNotIncludes(zh, '配置 .movscript/.codex 下的 Codex 工作区档案')
  assertIncludes(zh, '"settingsSnapshotHelp": "导出会包含 Provider 模型')
  assertIncludes(zh, '移除 Base URL 中的 URL 密钥凭据')
  assertIncludes(zh, '"settingsSnapshotSummary": "快照摘要"')
  assertIncludes(zh, '"loadSettingsSnapshotFile": "载入文件"')
  assertIncludes(zh, '"downloadSettings": "下载"')
  assertIncludes(zh, '"settingsDownloaded": "配置快照已下载。"')
  assertIncludes(zh, '"settingsSnapshotInvalid": "快照不可导入')
  assertIncludes(zh, '"settingsSnapshotTooLarge": "快照超过 {{size}} 上限。"')
  assertIncludes(zh, '"settingsSnapshotModelCatalogUnavailable": "快照引用了后端模型配置')
  assertIncludes(zh, '"settingsSnapshotCatalogUnavailable": "快照引用了配置文件')
  assertIncludes(zh, '"settingsSnapshotCapabilitiesUnavailable": "快照引用了工具权限')
  assertIncludes(zh, '"settingsSnapshotModelTargetMissing": "快照包含模型配置')
  assertIncludes(zh, '"settingsSnapshotToolPermissionsInvalid": "快照工具权限有 {{count}} 个不可导入项。"')
  assertIncludes(zh, '"settingsSnapshotFileLoaded": "已载入 {{fileName}}')
  assertIncludes(zh, '"copySettingsStatus": "复制状态"')
  assertIncludes(zh, '"settingsStatusSummaryTitle": "Agent Settings 状态摘要"')
  assertNotIncludes(zh, '"openDebug": "打开调试"')
  assertIncludes(zh, '"refresh": "刷新"')
  assertIncludes(zh, '"settingsSnapshotImpactPreview": "导入影响预览"')
  assertIncludes(zh, '"copySettingsSnapshotImpact": "复制影响"')
  assertIncludes(zh, '"settingsSnapshotImpactSummaryTitle": "Agent Settings 快照导入影响"')
  assertIncludes(zh, '"model": "Provider 模型"')
  assertIncludes(zh, '"config": "写入配置文件"')
  assertIncludes(zh, '"local": "写入本地"')
  assertIncludes(zh, '"providerSessionLimits": "Provider 会话限制"')
  assertIncludes(zh, '"limits": "运行限制"')
  assertIncludes(zh, '"limits": "导入时会把 {{count}} 项运行限制写入目标配置文件。"')
  assertIncludes(zh, '"model": "导入时会把模型 {{model}} 写入 Provider 模型设置，不会改写配置文件。"')
  assertIncludes(zh, '"tools": "导入时会保存 {{count}} 条配置文件工具授权。"')
  assertIncludes(zh, '"model": "Provider 模型和 Provider API 模式。"')
  assertIncludes(zh, '"maxToolCalls": "工具调用上限"')
  assertIncludes(zh, '"maxIterations": "迭代上限"')
  assertIncludes(zh, '"settingsSnapshotLimitsTargetMissing": "快照包含运行限制')
  assertIncludes(zh, '"settingsImportBackup": "导入前备份"')
  assertIncludes(zh, '"restoreImportBackup": "恢复备份"')
  assertIncludes(zh, '"clearImportBackup": "清除备份"')
  assertIncludes(zh, '"settingsBackupCleared": "导入前备份已清除。"')
  assertIncludes(zh, '"settingsBackupRestored": "备份已恢复')
  assertIncludes(zh, '"settingsImportDoneWithBackup": "配置快照已导入，并已保留导入前备份。"')
  assertIncludes(zh, '"settingsAuditPanel": "配置变更审计"')
  assertIncludes(zh, '"copySettingsAudit": "复制审计"')
  assertIncludes(zh, '"settingsAuditSummaryTitle": "Agent Settings 配置变更审计"')
  assertIncludes(zh, '"settings_changed": "配置变更"')
  assertIncludes(zh, '"modelPanel": "Provider 模型"')
  assertIncludes(zh, '"modelLabel": "Provider 文本模型"')
  assertIncludes(zh, '"modelHelp": "Base URL 留空或指向 MovScript 后端时，可从后端已启用的 text/reasoning 能力模型中选择，并保存为 Provider 模型设置。"')
  assertIncludes(zh, '"activeConfigFileHelp": "选择只切换正在查看和编辑的配置文件；点击设为激活后，后续新 provider-session run 才会使用它。"')
  assertNotIncludes(zh, '新 Agent Run')
  assertNotIncludes(zh, '新 Agent 能力')
  assertNotIncludes(zh, '新增 Agent 工作')
  assertIncludes(zh, '"modelSaved": "已保存 Provider 模型配置：{{model}}（{{apiKind}} / {{routes}}）"')
  assertIncludes(zh, '"modelTested": "已保存并测试 Provider 模型配置：{{model}}（{{apiKind}} / {{routes}}）"')
  assertIncludes(zh, '"testMessageDefault": "请用一句话回复：provider 模型连接正常。"')
  assertNotIncludes(zh, 'Agent Provider 模型连接正常')
  assertIncludes(zh, '"clearModelConfig": "清除配置"')
  assertIncludes(zh, '"clearModelConfigConfirm": "确认清除"')
  assertIncludes(zh, '"model_cleared": "清除"')
  assertIncludes(zh, '"modelCleared": "已清除 Provider 模型配置"')
  assertIncludes(zh, '"settings_operation_failed": "操作失败"')
  assertIncludes(zh, '"operationFailed": "{{operation}} 操作失败：{{error}}"')
  assertIncludes(zh, '"settingsSnapshotImported": "已导入配置快照')
  assertIncludes(zh, '"settingsSnapshotRestored": "已从备份快照恢复配置')
  assertIncludes(zh, '"readinessPanel": "配置就绪度"')
  assertIncludes(zh, '"copyReadiness": "复制就绪度"')
  assertIncludes(zh, '"readinessSummaryTitle": "Agent Settings 配置就绪度"')
  assertIncludes(zh, '"modelMissing": "当前配置文件尚未保存模型配置。"')
  assertIncludes(zh, '"ready": "就绪"')
  assertIncludes(zh, '"actionItemsPanel": "待处理配置问题"')
  assertIncludes(zh, '"copyActionItems": "复制问题"')
  assertIncludes(zh, '"actionItemsSummaryTitle": "Agent Settings 待处理配置问题"')
  assertIncludes(zh, '"modelBaseURLSensitive": "模型 Base URL 可能包含密钥"')
  assertIncludes(zh, 'Base URL 包含 username/password 或 token')
  assertIncludes(zh, '"baseUrlSecretsBlocked": "Base URL 包含 username/password')
  assertIncludes(zh, '"stripSensitiveBaseURLQuery": "移除敏感参数"')
  assertIncludes(zh, '"useRuntimeEnvForSecrets": "不要把密钥写入 URL')
  assertIncludes(zh, '"actionItemsSummarySection": "设置区块：{{section}}"')
  assertIncludes(zh, '"actionItemsSummaryQuickFix": "可用快速修复：{{quickFix}}"')
  assertIncludes(zh, '"actionItemsCountSummary": "{{actions}} 个需处理，{{warnings}} 个需确认"')
  assertIncludes(zh, '"skillConfigInvalid": "当前配置文件的 Skill 选择存在冲突"')
  assertIncludes(zh, '"routesInvalid": "模型路由不可用"')
  assertIncludes(zh, '"toolPermissionsUnsaved": "工具权限有未保存变更"')
  assertIncludes(zh, '"skillDependency": "{{skillId}} 依赖 {{dependencyId}}"')
  assertIncludes(zh, '"toolUnavailableReason": "{{reason}}：{{count}} 个工具"')
  assertIncludes(zh, '"jumpToSection": "定位"')
  assertIncludes(zh, '"resetWorkspace": "重置工作区"')
  assertIncludes(zh, '"enableChatRoute": "启用对话路由"')
  assertIncludes(zh, '"applied": "本地修复已应用')
  assertIncludes(zh, '"saveAfterQuickFix": "快速修复只改本地工作区')
  assertNotIncludes(zh, '"configurationMapPanel": "配置地图"')
  assertNotIncludes(zh, '"managementShortcutsPanel": "Skills / Tools 管理视图"')
  assertNotIncludes(zh, '"managementShortcutsHelp": "直接打开目录、配置文件或运行时视图')
  assertNotIncludes(zh, '"focusedManagementRoute": "当前只显示')
  assertNotIncludes(zh, legacyAgentTerm('runtime', 'Limit', 'SetsPanel'))
  assertNotIncludes(zh, legacyAgentTerm('runtime', 'Limit', 'Set'))
  assertNotIncludes(zh, legacyAgentTerm('runtime', '_limit', '_set'))
  assertIncludes(zh, '"enabled": "已启用"')
  assertIncludes(en, '"apiKindLabel": "Call mode"')
  assertIncludes(en, '"providerModelIdLabel": "Provider model ID"')
  assertIncludes(en, '"providerModelIdHelp": "For direct OpenAI or Anthropic modes')
  assertIncludes(en, '"providerModelPanel": "Provider Model"')
  assertIncludes(en, '"providerModelPanelHelp": "Enter the model ID. When Base URL is empty')
  assertIncludes(en, '"modelRequired": "Select a model or enter a provider model ID first."')
  assertIncludes(en, '"modelIdSecretsBlocked": "The provider model ID looks like it contains an API key')
  assertIncludes(en, '"providerApiKeyLabel": "API key"')
  assertIncludes(en, '"providerCredentialHelp": "Only required when Base URL points to an external provider')
  assertIncludes(en, '"modelCredentialStatus"')
  assertIncludes(en, '"modelCredentials": "Model credentials"')
  assertIncludes(en, '"modelIdSensitive": "Saved model ID may contain secrets"')
  assertIncludes(en, '"confirmClearModelConfig": "Prepare Clear Model Config"')
  assertIncludes(en, '"modelIdSensitive": "The saved direct Provider model ID looks like it contains an API key')
  assertIncludes(en, '"modelIdSensitive": "The saved model ID looks like it contains secrets; settings snapshot export will skip this model config."')
  assertIncludes(en, '"modelCredentialsMissing": "Direct model credentials are not loaded"')
  assertIncludes(en, '"modelCredentialsReady": "API key settings loaded model credentials from {{env}}."')
  assertIncludes(en, '"controlPlane": "Control plane')
  assertIncludes(en, '"futureRuns": "Does not show per-run diagnostics"')
  assertIncludes(en, '"debugReadOnly": "Provider session observation remains read-only"')
  assertIncludes(en, '"openaiResponses": "OpenAI / Responses"')
  assertIncludes(en, '"apiModeCapabilityPanel": "Call Mode Capability Matrix"')
  assertIncludes(en, '"recommended": "Recommended for new provider sessions"')
  assertIncludes(en, '"agenticPrimitive"')
  assertNotIncludes(en, '"legacyCompatibility"')
  assertIncludes(en, '"apiMode": "Call mode"')
  assertIncludes(en, '"apiModeResponsesRecommended": "OpenAI Responses is the recommended path for new provider capabilities."')
  assertIncludes(en, '"apiModeCompatibility": "Call mode is still compatibility mode"')
  assertIncludes(en, '"switchOpenAIResponses": "Switch to Responses"')
  assertIncludes(en, '"switchedToResponses": "Local workspace switched to OpenAI Responses')
  assertIncludes(en, '"apiModeSwitchPlanTitle": "Call Mode Switch TaskGraph"')
  assertIncludes(en, '"copyApiModeSwitchTaskGraph": "Copy switch taskGraph"')
  assertIncludes(en, '"targetModeMigration": "{{apiKind}} should migrate toward {{targetApiKind}}')
  assertIncludes(en, '"settings_quick_fix_mode_migration": "Mode migration"')
  assertIncludes(en, '"settings_quick_fix_sensitive_cleanup": "Sensitive cleanup"')
  assertIncludes(en, '"quickFixApplied": "Applied local quick fix')
  assertIncludes(en, '"quickFixAppliedImmediate": "Applied quick fix')
  assertIncludes(en, 'local config updated')
  assertIncludes(en, '"modelRoutesPanel": "Model Routes"')
  assertIncludes(en, '"allRoutesDisabled": "Enable at least one')
  assertIncludes(en, '"modelRouteModel": "Model"')
  assertIncludes(en, '"settingsSnapshotPanel": "Config Import / Export"')
  assertIncludes(en, '.movscript/providers/codex')
  assertIncludes(en, '.movscript/providers/mova')
  assertNotIncludes(en, 'under .movscript/.codex')
  assertIncludes(en, '"settingsSnapshotHelp": "Export includes the Provider model')
  assertIncludes(en, 'strips secret URL credentials from Base URLs')
  assertIncludes(en, '"settingsSnapshotSummary": "Snapshot Summary"')
  assertIncludes(en, '"loadSettingsSnapshotFile": "Load File"')
  assertIncludes(en, '"downloadSettings": "Download"')
  assertIncludes(en, '"settingsDownloaded": "Settings snapshot downloaded."')
  assertIncludes(en, '"settingsSnapshotInvalid": "Snapshot cannot be imported')
  assertIncludes(en, '"settingsSnapshotTooLarge": "Snapshot exceeds the {{size}} limit."')
  assertIncludes(en, '"settingsSnapshotModelCatalogUnavailable": "The snapshot references a backend model config')
  assertIncludes(en, '"settingsSnapshotCatalogUnavailable": "The snapshot references config files')
  assertIncludes(en, '"settingsSnapshotCapabilitiesUnavailable": "The snapshot references tool permissions')
  assertIncludes(en, '"settingsSnapshotModelTargetMissing": "The snapshot includes model configuration')
  assertIncludes(en, '"settingsSnapshotToolPermissionsInvalid": "Snapshot tool permissions have {{count}} items that cannot be imported."')
  assertIncludes(en, '"settingsSnapshotFileLoaded": "Loaded {{fileName}}')
  assertIncludes(en, '"copySettingsStatus": "Copy Status"')
  assertIncludes(en, '"settingsStatusSummaryTitle": "Agent Settings Status Summary"')
  assertNotIncludes(en, '"openDebug": "Open Debug"')
  assertIncludes(en, '"refresh": "Refresh"')
  assertIncludes(en, '"settingsSnapshotImpactPreview": "Import Impact Preview"')
  assertIncludes(en, '"copySettingsSnapshotImpact": "Copy Impact"')
  assertIncludes(en, '"settingsSnapshotImpactSummaryTitle": "Agent Settings Snapshot Import Impact"')
  assertIncludes(en, '"model": "Provider model"')
  assertIncludes(en, '"config": "Writes Config File"')
  assertIncludes(en, '"local": "Writes Local"')
  assertIncludes(en, '"runtimeLimits": "Provider session limits"')
  assertIncludes(en, '"limits": "Provider session limits"')
  assertIncludes(en, '"limits": "Import writes {{count}} provider-session limits into the target config file."')
  assertIncludes(en, '"model": "Import writes model {{model}} into Provider model settings and does not alter config files."')
  assertIncludes(en, '"tools": "Import saves {{count}} config-file tool grants."')
  assertIncludes(en, '"model": "Provider model and provider API mode."')
  assertIncludes(en, '"maxToolCalls": "Tool-call limit"')
  assertIncludes(en, '"maxIterations": "Iteration limit"')
  assertIncludes(en, '"settingsSnapshotLimitsTargetMissing": "The snapshot includes provider-session limits')
  assertIncludes(en, '"settingsImportBackup": "Pre-import Backup"')
  assertIncludes(en, '"restoreImportBackup": "Restore Backup"')
  assertIncludes(en, '"clearImportBackup": "Clear Backup"')
  assertIncludes(en, '"settingsBackupCleared": "Pre-import backup cleared."')
  assertIncludes(en, '"settingsBackupRestored": "Backup restored')
  assertIncludes(en, '"settingsImportDoneWithBackup": "Settings snapshot imported and pre-import backup retained."')
  assertIncludes(en, '"settingsAuditPanel": "Configuration Change Audit"')
  assertIncludes(en, '"copySettingsAudit": "Copy Audit"')
  assertIncludes(en, '"settingsAuditSummaryTitle": "Agent Settings Configuration Change Audit"')
  assertIncludes(en, '"settings_changed": "Settings changed"')
  assertIncludes(en, '"settings_operation_failed": "Operation failed"')
  assertIncludes(en, '"operationFailed": "{{operation}} operation failed: {{error}}"')
  assertIncludes(en, '"settingsSnapshotImported": "Imported settings snapshot')
  assertIncludes(en, '"settingsSnapshotRestored": "Restored settings from backup snapshot')
  assertIncludes(en, '"modelPanel": "Provider Model"')
  assertIncludes(en, '"modelLabel": "Provider text model"')
  assertIncludes(en, '"modelHelp": "When Base URL is empty or points to the MovScript backend, select an enabled backend text/reasoning model and save it into Provider model settings."')
  assertIncludes(en, '"activeConfigFileHelp": "Selection only changes the config file being viewed and edited; set it active before future new provider-session runs use it."')
  assertNotIncludes(en, 'new Agent runs')
  assertNotIncludes(en, 'new Agent capabilities')
  assertNotIncludes(en, 'new Agent work')
  assertIncludes(en, '"modelSaved": "Saved Provider model config: {{model}} ({{apiKind}} / {{routes}})"')
  assertIncludes(en, '"modelTested": "Saved and tested Provider model config: {{model}} ({{apiKind}} / {{routes}})"')
  assertIncludes(en, '"testMessageDefault": "Reply in one sentence: the provider model connection is working."')
  assertNotIncludes(en, 'Agent Provider model connection')
  assertIncludes(en, '"clearModelConfig": "Clear Config"')
  assertIncludes(en, '"clearModelConfigConfirm": "Confirm Clear"')
  assertIncludes(en, '"model_cleared": "Cleared"')
  assertIncludes(en, '"modelCleared": "Cleared Provider model config"')
  assertIncludes(en, '"readinessPanel": "Configuration Readiness"')
  assertIncludes(en, '"copyReadiness": "Copy Readiness"')
  assertIncludes(en, '"readinessSummaryTitle": "Agent Settings Configuration Readiness"')
  assertIncludes(en, '"modelMissing": "The current config file has no saved model config."')
  assertIncludes(en, '"ready": "Ready"')
  assertIncludes(en, '"actionItemsPanel": "Configuration Action Items"')
  assertIncludes(en, '"copyActionItems": "Copy Issues"')
  assertIncludes(en, '"actionItemsSummaryTitle": "Agent Settings Configuration Action Items"')
  assertIncludes(en, '"modelBaseURLSensitive": "Model Base URL may contain secrets"')
  assertIncludes(en, 'Base URL contains username/password credentials')
  assertIncludes(en, '"baseUrlSecretsBlocked": "The Base URL contains username/password')
  assertIncludes(en, '"stripSensitiveBaseURLQuery": "Remove Sensitive Params"')
  assertIncludes(en, '"useRuntimeEnvForSecrets": "Do not store secrets in URLs')
  assertIncludes(en, '"actionItemsSummarySection": "Settings section: {{section}}"')
  assertIncludes(en, '"actionItemsSummaryQuickFix": "Available quick fix: {{quickFix}}"')
  assertIncludes(en, '"actionItemsCountSummary": "{{actions}} actions, {{warnings}} reviews"')
  assertIncludes(en, '"skillConfigInvalid": "The current config file\'s Skill selection has conflicts"')
  assertIncludes(en, '"routesInvalid": "Model routes are unavailable"')
  assertIncludes(en, '"toolPermissionsUnsaved": "Tool permission has unsaved changes"')
  assertIncludes(en, '"skillDependency": "{{skillId}} depends on {{dependencyId}}"')
  assertIncludes(en, '"toolUnavailableReason": "{{reason}}: {{count}} tools"')
  assertIncludes(en, '"jumpToSection": "Locate"')
  assertIncludes(en, '"resetWorkspace": "Reset Workspace"')
  assertIncludes(en, '"enableChatRoute": "Enable Chat Route"')
  assertIncludes(en, '"applied": "Local fix applied')
  assertIncludes(en, '"saveAfterQuickFix": "Quick fix only changes the local workspace')
  assertNotIncludes(en, '"configurationMapPanel": "Configuration Map"')
  assertNotIncludes(en, '"managementShortcutsPanel": "Skills / Tools Management Views"')
  assertNotIncludes(en, '"managementShortcutsHelp": "Open Catalog, Config Files, or Runtime directly')
  assertNotIncludes(en, '"focusedManagementRoute": "Showing only')
  assertNotIncludes(en, legacyAgentTerm('runtime', 'Limit', 'SetsPanel'))
  assertNotIncludes(en, legacyAgentTerm('runtime', 'Limit', 'Set'))
  assertNotIncludes(en, legacyAgentTerm('runtime', '_limit', '_set'))
})

test('agent settings exposes skill catalog management controls', () => {
  const source = readFileSync(resolve('src/features/agent/components/AIAgentSettingsPage.tsx'), 'utf8')
  const clientSource = readProviderSessionClientContractSource()
  const protocolSource = readFileSync(resolve('../../packages/core/src/agent/protocol.ts'), 'utf8')
  const routeSource = readFileSync(resolve('src/routes/projectRoutes.ts'), 'utf8')
  const appSource = readFileSync(resolve('src/App.tsx'), 'utf8')
  const zh = readFileSync(resolve('src/i18n/locales/zh-CN.json'), 'utf8')
  const en = readFileSync(resolve('src/i18n/locales/en-US.json'), 'utf8')

  assertIncludes(source, "queryKey: ['agent-settings-skill-catalog'")
  assertIncludes(source, 'settingsProviderSessionClient.inspect()')
  assertNotIncludes(source, 'providerSessionClient.reloadProviderCatalog()')
  assertNotIncludes(source, "action: 'catalog_reloaded'")
  assertNotIncludes(source, 'agents.settings.auditSummaries.catalogReloaded')
  assertNotIncludes(source, "target: 'installed_capabilities'")
  assertNotIncludes(source, 'providerSessionClient.uninstallAgentPack')
  assertNotIncludes(source, 'providerSessionClient.installAgentPack')
  assertNotIncludes(source, 'packFileInputRef')
  assertNotIncludes(source, 'loadPackFile')
  assertNotIncludes(source, 'packWorkspaceValidation')
  assertNotIncludes(source, 'data-testid="agent-settings-pack-workspace-summary"')
  assertNotIncludes(source, 'data-testid="agent-settings-pack-workspace-error"')
  assertNotIncludes(source, 'uninstallPack')
  assertNotIncludes(source, 'packUninstallPluginId')
  assertNotIncludes(source, 'data-testid="agent-settings-uninstall-plugin-id-error"')
  assertNotIncludes(source, 'packUninstallConfirmPluginId')
  assertNotIncludes(source, 'data-testid="agent-settings-installed-pack-uninstall"')
  assertNotIncludes(source, 'packPlugins')
  assertNotIncludes(source, 'pluginCatalog?.packPlugins')
  assertNotIncludes(source, 'id="agent-settings-installed-capabilities"')
  assertNotIncludes(source, 'data-testid="agent-settings-installed-pack-list"')
  assertNotIncludes(source, 'data-testid="agent-settings-pack-row"')
  assertNotIncludes(source, 'buildPackStats')
  assertNotIncludes(source, "pack.source === 'local'")
  assertNotIncludes(source, "pack.source === 'team'")
  assertNotIncludes(source, 'pack.reference?.length')
  assertNotIncludes(source, 'packStats.references')
  assertNotIncludes(source, 'agents.settings.packFields.references')
  assertNotIncludes(source, 'data-testid="agent-settings-pack-registered-items"')
  assertNotIncludes(source, 'packRegisteredItemSummary')
  assertNotIncludes(source, 'agents.settings.packRegisteredSkills')
  assertNotIncludes(source, 'agents.settings.packRegisteredTools')
  assertNotIncludes(source, 'agents.settings.packRegisteredSchemas')
  assertNotIncludes(source, 'agents.settings.packRegisteredReferences')
  assertNotIncludes(source, 'function buildPackHealth')
  assertNotIncludes(source, 'pack.requires?.packs')
  assertNotIncludes(source, 'versionSatisfies')
  assertNotIncludes(source, 'data-testid="agent-settings-pack-requires"')
  assertNotIncludes(source, 'data-testid="agent-settings-pack-missing-requirements"')
  assertNotIncludes(source, 'data-testid="agent-settings-pack-incompatible-requirements"')
  assertNotIncludes(source, 'data-testid="agent-settings-pack-enabled-conflicts"')
  assertNotIncludes(source, 'providerSessionClient.saveSkillInstructions')
  assertIncludes(source, 'buildSkillStats')
  assertNotIncludes(source, 'buildSkillRuntimeStats')
  assertNotIncludes(source, 'buildSkillCatalogStats')
  assertNotIncludes(source, 'buildSkillManagementItems')
  assertNotIncludes(source, 'SkillTrustLevel')
  assertNotIncludes(source, 'skillTrustLevel')
  assertNotIncludes(source, 'skillTrustLabel')
  assertNotIncludes(source, 'skillGovernanceStats')
  assertIncludes(source, 'function filterSkills')
  assertIncludes(source, 'data-testid="agent-settings-skill-filters"')
  assertIncludes(source, 'data-testid="agent-settings-skill-search"')
  assertIncludes(source, 'data-testid="agent-settings-skill-source-filter"')
  assertNotIncludes(source, 'data-testid="agent-settings-skill-tag-filter"')
  assertNotIncludes(source, 'data-testid="agent-settings-skill-load-mode-filter"')
  assertIncludes(source, 'skillSourceKind(skill)')
  assertIncludes(source, 'skill.source ??')
  assertIncludes(source, "'team', 'mcp'")
  assertIncludes(source, 'const configSkillIds = new Set(configFile?.skillIds ?? [])')
  assertIncludes(source, 'configSkillIds.has(skill.id)')
  assertNotIncludes(source, 'skill.runtime?.contextBehavior')
  assertNotIncludes(source, 'skill.runtime?.defaultActivation')
  assertNotIncludes(source, 'skill.runtime?.toolGrantNames.length')
  assertIncludes(source, 'skill.toolGrants ?? []')
  assertNotIncludes(source, 'skillTriggerSummary(skill.triggers)')
  assertNotIncludes(source, 'function skillTriggerSearchValues')
  assertNotIncludes(source, 'agents.settings.skillFields.triggers')
  assertNotIncludes(source, 'skillContextBudgetSummary(skill.contextBudget)')
  assertNotIncludes(source, 'function skillContextBudgetSearchValues')
  assertNotIncludes(source, 'agents.settings.skillFields.contextBudget')
  assertIncludes(protocolSource, 'triggers?: JSONValue[]')
  assertIncludes(protocolSource, 'toolGrants?: string[]')
  assertIncludes(protocolSource, 'contextBudget?:')
  assertIncludes(protocolSource, "source?: 'builtin' | 'local' | 'plugin' | 'team' | 'mcp'")
  assertNotIncludes(routeSource, "agentSettingsManagement: '/agent/settings/:managementArea/:managementView'")
  assertNotIncludes(routeSource, 'export function agentSettingsManagementPath')
  assertNotIncludes(appSource, '<Route path={ROUTES.agentSettingsManagement} element={<AIAgentSettingsPage />} />')
  assertNotIncludes(source, "useParams<{ managementArea?: string; managementView?: string }>()")
  assertNotIncludes(source, 'parseAgentManagementArea(routeParams.managementArea)')
  assertNotIncludes(source, 'parseAgentManagementView(routeParams.managementView)')
  assertNotIncludes(source, 'function selectManagementView(area: AgentManagementArea, view: AgentManagementView)')
  assertNotIncludes(source, 'navigate(`${agentSettingsManagementPath(area, view)}${search}`)')
  assertNotIncludes(source, "selectManagementView('skills', 'config')")
  assertNotIncludes(source, 'testId="agent-settings-skill-management-map"')
  assertNotIncludes(source, "const [skillManagementView, setSkillManagementView] = useState<AgentManagementView>(urlSkillManagementView ?? 'runtime')")
  assertNotIncludes(source, 'testId="agent-settings-skill-management-view"')
  assertNotIncludes(source, "onChange={(view) => selectManagementView('skills', view)}")
  assertNotIncludes(source, "skillManagementView === 'catalog'")
  assertNotIncludes(source, "skillManagementView === 'config'")
  assertNotIncludes(source, "skillManagementView === 'runtime'")
  assertNotIncludes(source, 'data-testid="agent-settings-skill-catalog-section"')
  assertNotIncludes(source, 'data-testid="agent-settings-skill-catalog-summary"')
  assertNotIncludes(source, 'data-testid="agent-settings-skill-governance"')
  assertNotIncludes(source, 'data-testid="agent-settings-skill-config-section"')
  assertNotIncludes(source, 'data-testid="agent-settings-skill-runtime-section"')
  assertNotIncludes(source, 'data-testid="agent-settings-skill-runtime-list"')
  assertNotIncludes(source, 'const runtimeSkills = useMemo')
  assertNotIncludes(source, 'skillRuntimeRank')
  assertNotIncludes(source, 'agents.settings.skillRuntimeListPanel')
  assertNotIncludes(source, 'agents.settings.skillRuntimeListHelp')
  assertNotIncludes(source, 'agents.settings.managementLayers.catalog')
  assertNotIncludes(source, 'agents.settings.managementViewSwitch')
  assertNotIncludes(source, 'agents.settings.skillManagementDetails.runtime')
  assertNotIncludes(source, 'agents.settings.skillManagementLayerHelp.catalog')
  assertNotIncludes(source, 'agents.settings.skillManagementLayerHelp.config')
  assertNotIncludes(source, 'agents.settings.skillManagementLayerHelp.runtime')
  assertNotIncludes(source, 'agents.settings.skillRuntimeFields.activation')
  assertNotIncludes(source, 'agents.settings.skillRuntimeFields.context')
  assertNotIncludes(source, 'agents.settings.skillRuntimeFields.toolGrants')
  assertNotIncludes(source, 'agents.settings.skillRuntimeActivation.')
  assertIncludes(source, 'buildSkillConfigWorkspaces')
  assertIncludes(source, 'buildSkillConfigIssues')
  assertNotIncludes(source, 'parsePackInput')
  assertNotIncludes(source, 'MAX_PACK_FILES')
  assertNotIncludes(source, 'MAX_PACK_FILE_BYTES')
  assertNotIncludes(source, 'MAX_PACK_TOTAL_BYTES')
  assertNotIncludes(source, 'byteLength(file.content)')
  assertNotIncludes(source, 'const seenPaths = new Set<string>()')
  assertNotIncludes(source, 'Pack may include at most')
  assertNotIncludes(source, 'Pack total content is too large')
  assertNotIncludes(source, 'isSafePackPluginId')
  assertNotIncludes(source, 'isSafePackFilePath')
  assertIncludes(source, 'skillConfigIssues.length > 0')
  assertIncludes(source, 'skillConfigHasIssues')
  assertIncludes(source, "agents.settings.readinessDetails.skillsInvalid")
  assertIncludes(source, 'function SkillRow')
  assertIncludes(source, 'skillConfigEditHelp')
  assertNotIncludes(source, 'skillInstructionLabel={workspace')
  assertNotIncludes(source, 'skillInstructionHelp')
  assertNotIncludes(source, 'data-testid="agent-settings-skill-instruction-editor"')
  assertIncludes(source, 'buildSkillConfigChanges')
  assertIncludes(source, 'skill.dependencies')
  assertIncludes(source, 'skill.conflicts')
  assertIncludes(clientSource, "reloadProviderCatalog(signal?: AbortSignal): Promise<unknown>")
  assertIncludes(clientSource, "return this.postJSON(providerCatalogWireRoute('catalog', 'reload'), {}, signal)")
  assertIncludes(clientSource, "function providerCatalogWireRoute(kind: 'catalog' | 'config-files' | 'skills', suffix?: string): string")
  assertNotIncludes(protocolSource, 'packPlugins?: Array<')
  assertNotIncludes(source, "saveSkillInstructions(input: { skills: Array<{ id: string; instructionTemplate: string }> }")
  assertIncludes(zh, '"skillsPanel": "Skills 管理"')
  assertNotIncludes(zh, '"skillManagementMapPanel": "Skill 管理地图"')
  assertNotIncludes(zh, '"skillManagementLayerHelp"')
  assertNotIncludes(zh, '"skillRuntimeListPanel": "运行时 Skill 清单"')
  assertNotIncludes(zh, '"skillRuntimeListHelp": "只读展示后端解释过的运行时状态')
  assertIncludes(zh, '"catalog": "目录"')
  assertNotIncludes(zh, '"managementViewSwitch": "管理视图"')
  assertNotIncludes(zh, '"runtime": "{{baseContext}} 个进入基础上下文')
  assertNotIncludes(zh, '"activation": "激活"')
  assertNotIncludes(zh, '"baseContext": "基础上下文"')
  assertNotIncludes(zh, '"base_context": "基础上下文"')
  assertIncludes(zh, '"saveSkillConfig": "保存 Skill 配置"')
  assertIncludes(zh, '"skillInstructionHelp": "Skill 内容编辑放在 Plugins')
  assertIncludes(zh, '"skillConfigIssues": "当前配置文件的 Skill 选择存在依赖或冲突问题"')
  assertIncludes(zh, '"skillsInvalid": "当前配置文件的 Skill 选择存在 {{count}} 个依赖或冲突问题。"')
  assertNotIncludes(zh, '"triggers": "触发器"')
  assertNotIncludes(zh, '"contextBudget": "上下文预算"')
  assertIncludes(zh, '"skillFiltersPanel": "Skill 筛选"')
  assertIncludes(zh, '"skillSearchPlaceholder": "搜索 Skill 名称')
  assertIncludes(zh, '"skillFilterResult": "当前显示 {{count}} / {{total}} 个 Skills。"')
  assertIncludes(zh, '"skillFiltersHelp": "按来源筛选 Skill')
  assertIncludes(zh, '"reloadCatalog": "重载目录"')
  assertIncludes(zh, '"catalog_reloaded": "重载目录"')
  assertIncludes(zh, '"catalogReloaded": "已重载 Provider 目录"')
  assertIncludes(en, '"skillsPanel": "Skills Management"')
  assertNotIncludes(en, '"skillManagementMapPanel": "Skill management map"')
  assertIncludes(en, '"skillCatalogSummaryPanel": "Skill catalog summary"')
  assertIncludes(en, '"skillCatalogSummaryHelp": "Review version coverage and source')
  assertNotIncludes(en, '"skillGovernancePanel"')
  assertNotIncludes(en, '"skillTrustLevels"')
  assertNotIncludes(en, '"skillManagementLayerHelp"')
  assertNotIncludes(en, '"skillRuntimeListPanel": "Runtime skill list"')
  assertNotIncludes(en, '"skillRuntimeListHelp": "Read-only backend explanations for provider-session state')
  assertIncludes(en, '"catalog": "Catalog"')
  assertNotIncludes(en, '"managementViewSwitch": "Management view"')
  assertNotIncludes(en, '"runtime": "{{baseContext}} enter base context')
  assertNotIncludes(en, 'source(s) need review')
  assertNotIncludes(zh, '来源需要复核')
  assertNotIncludes(en, '"activation": "Activation"')
  assertNotIncludes(en, '"baseContext": "Base context"')
  assertNotIncludes(en, '"base_context": "Base context"')
  assertIncludes(en, '"saveSkillConfig": "Save Skill Config"')
  assertIncludes(en, '"skillInstructionHelp": "Edit Skill content in Packs or the plugin marketplace')
  assertIncludes(en, '"skillConfigIssues": "The current config file\'s Skill selection has dependency or conflict issues"')
  assertIncludes(en, '"skillsInvalid": "The current config file\'s Skill selection has {{count}} dependency or conflict issues."')
  assertNotIncludes(en, '"triggers": "Triggers"')
  assertNotIncludes(en, '"contextBudget": "Context budget"')
  assertIncludes(en, '"skillFiltersPanel": "Skill filters"')
  assertIncludes(en, '"skillSearchPlaceholder": "Search Skill name')
  assertIncludes(en, '"skillFilterResult": "Showing {{count}} / {{total}} Skills."')
  assertIncludes(en, '"skillFiltersHelp": "Filter Skills by source')
  assertIncludes(en, '"reloadCatalog": "Reload Catalog"')
  assertIncludes(en, '"catalog_reloaded": "Catalog reloaded"')
  assertIncludes(en, '"catalogReloaded": "Reloaded Provider catalog"')
})

test('agent settings exposes config-file capability config controls', () => {
  const source = readFileSync(resolve('src/features/agent/components/AIAgentSettingsPage.tsx'), 'utf8')
  const clientSource = readProviderSessionClientContractSource()
  const protocolSource = readFileSync(resolve('../../packages/core/src/agent/protocol.ts'), 'utf8')
  const storeSource = readFileSync(resolve('src/features/agent/state/agentStore.ts'), 'utf8')
  const zh = readFileSync(resolve('src/i18n/locales/zh-CN.json'), 'utf8')
  const en = readFileSync(resolve('src/i18n/locales/en-US.json'), 'utf8')

  assertIncludes(clientSource, 'ProviderCatalogPack,')
  assertIncludes(clientSource, 'ProviderCatalogConfigFile,')
  assertIncludes(protocolSource, 'packs: ProviderCatalogPack[]')
  assertIncludes(protocolSource, 'configFiles: ProviderCatalogConfigFile[]')
  assertIncludes(clientSource, "saveActiveProviderConfigFile(input: { configFileId: string }")
  assertIncludes(clientSource, 'saveProviderConfigFile(input: { configFile: ProviderCatalogConfigFile; activate?: boolean }')
  assertIncludes(clientSource, 'deleteProviderConfigFile(input: { configFileId: string }')
  assertNotIncludes(clientSource, 'saveActiveAgentConfigFile')
  assertNotIncludes(clientSource, 'saveAgentConfigFile')
  assertNotIncludes(clientSource, 'deleteAgentConfigFile')
  assertIncludes(source, 'function currentProviderConfigFileId')
  assertIncludes(source, 'function ConfigFileRow')
  assertIncludes(source, 'function ConfigFileDiffPanel')
  assertIncludes(source, 'function duplicateSelectedConfigFile()')
  assertIncludes(source, 'function createBlankConfigFile()')
  assertIncludes(source, 'function copySelectedConfigFile()')
  assertIncludes(source, 'function downloadSelectedConfigFile()')
  assertIncludes(source, 'function loadConfigFileFile(file?: File | null)')
  assertIncludes(source, 'function saveSelectedConfigFileDetails()')
  assertIncludes(source, 'function deleteSelectedConfigFile()')
  assertIncludes(source, 'function restoreConfigFileRollbackBackup()')
  assertIncludes(source, 'buildConfigFileExportText(selectedConfigFile)')
  assertIncludes(source, 'parseConfigFileExport(await file.text())')
  assertIncludes(source, 'function buildConfigFileRollbackBackup')
  assertNotIncludes(source, 'function configFileToolPermissionOverridesForBackup')
  assertIncludes(source, 'const rollbackConfigFile = currentConfigFile ? duplicateSnapshotConfigFile(currentConfigFile) : null')
  assertNotIncludes(source, 'configFileRollbackBackup.toolPermissionOverrides')
  assertNotIncludes(source, 'toolGrants: configFileRollbackBackup.toolPermissionOverrides')
  assertIncludes(source, 'lastConfigFileBackup')
  assertIncludes(source, 'data-testid="agent-settings-config-file-backup"')
  assertIncludes(source, 'data-testid="agent-settings-restore-config-file-backup"')
  assertIncludes(source, 'duplicateConfigFileForManagement')
  assertIncludes(source, 'createBlankConfigFileForManagement')
  assertIncludes(source, 'buildConfigFileDiff')
  assertIncludes(source, 'buildConfigFileDiff(currentConfigFile, selectedConfigFile, t)')
  assertIncludes(source, 'diffConfigFileApprovalDefaults')
  assertIncludes(source, 'diffConfigFileLimits')
  assertIncludes(source, 'configFileApprovalDefaultFieldLabel')
  assertIncludes(source, 'configFileApprovalValueLabel')
  assertIncludes(source, 'configFileLimitFieldLabel')
  assertIncludes(source, 'configFileApprovalDefaultSummaryValues')
  assertIncludes(source, 'configFileLimitSummaryValues')
  assertIncludes(source, 'configFileDiffSection(\'approvalDefaults\'')
  assertIncludes(source, 'configFileDiffSection(\'limits\'')
  assertIncludes(source, 'selectedConfigFileDiff')
  assertIncludes(source, 'configFileSwitchResetsToolPermissions')
  assertIncludes(source, 'agent-settings-installed-capabilities')
  assertNotIncludes(source, 'function PackRow')
  assertIncludes(source, 'settingsProviderSessionClient.saveActiveProviderConfigFile')
  assertIncludes(source, 'settingsProviderSessionClient.saveProviderConfigFile')
  assertIncludes(source, 'settingsProviderSessionClient.deleteProviderConfigFile')
  assertIncludes(source, 'data-testid="agent-settings-duplicate-config-file"')
  assertIncludes(source, 'data-testid="agent-settings-create-config-file"')
  assertIncludes(source, 'data-testid="agent-settings-copy-config-file"')
  assertIncludes(source, 'data-testid="agent-settings-download-config-file"')
  assertIncludes(source, 'data-testid="agent-settings-import-config-file"')
  assertIncludes(source, 'data-testid="agent-settings-config-file-message"')
  assertIncludes(source, 'data-testid="agent-settings-save-config-file-details"')
  assertIncludes(source, 'data-testid="agent-settings-config-file-name"')
  assertIncludes(source, 'data-testid="agent-settings-config-file-limits"')
  assertIncludes(source, 'data-testid="agent-settings-config-file-approval-defaults"')
  assertIncludes(source, "t('agents.settings.configFileScopeHelp')")
  assertIncludes(source, 'data-testid={`agent-settings-config-file-limit-${key}`}')
  assertIncludes(source, 'configFileApprovalDefaultWorkspaces')
  assertIncludes(source, 'data-testid="agent-settings-delete-config-file"')
  assertIncludes(source, 'selectedConfigFileId')
  assertIncludes(source, 'configFileNameWorkspace')
  assertIncludes(source, 'const raw = inspect?.activeConfigFileId')
  assertNotIncludes(source, 'inspect?.activeAgentManifest.metadata?.configFileId')
  assertIncludes(source, 'CONFIG_FILE_LIMIT_KEYS')
  assertIncludes(source, 'normalizeConfigFileLimitWorkspaces')
  assertIncludes(source, 'configFileLimitValue(limits, key)')
  assertIncludes(source, 'enabledPackIds')
  assertIncludes(source, 'toolGrants')
  assertIncludes(source, 'buildSkillConfigWorkspaces(catalogQuery.data?.skills ?? [], selectedConfigFile)')
  assertIncludes(source, 'buildConfigFileSkillIds(skillWorkspaces)')
  assertIncludes(source, 'hasSkillConfigSelectionChange')
  assertIncludes(source, 'skillIds: workspaceSkillIds')
  assertNotIncludes(source, 'skillInstructionChanges')
  assertNotIncludes(source, 'await providerSessionClient.saveSkillInstructions')
  assertIncludes(source, 'skillIds: buildConfigFileSkillIds(snapshot.skillConfig)')
  assertIncludes(source, 'settingsSnapshotSkillsTargetMissing')
  assertNotIncludes(source, 'await providerSessionClient.saveSkillInstructions({ skills: skillConfigChanges })')
  assertIncludes(storeSource, 'lastConfigFileBackup: AgentSettingsConfigFileBackup | null')
  assertIncludes(storeSource, "toolPermissionOverrides?: ProviderCatalogConfigFile['toolGrants']")
  assertIncludes(storeSource, 'function normalizeSettingsConfigFileBackup')
  assertIncludes(storeSource, 'normalizeSettingsBackupToolGrants(value.toolPermissionOverrides)')
  assertIncludes(storeSource, 'normalizeSettingsBackupConfigFile')
  assertIncludes(protocolSource, 'activeConfigFileId: string | null')
  assertIncludes(zh, '"configFilesPanel": "配置文件"')
  assertIncludes(zh, '"installedCapabilitiesPanel": "已安装能力"')
  assertIncludes(zh, '"activeConfigFileLabel": "选中配置文件"')
  assertIncludes(zh, '"saveConfigFile": "设为激活配置文件"')
  assertIncludes(zh, '"configFileSaved": "当前已激活"')
  assertNotIncludes(zh, 'defaultConfigFileLabel')
  assertIncludes(zh, '"configFileScopeHelp": "配置文件只是后续新 Run 的运行配置，不是 Agent 身份')
  assertIncludes(zh, '"saveConfigFileDetails": "保存详情"')
  assertIncludes(zh, '"configFileLimitsLabel": "运行限制"')
  assertIncludes(zh, '"configFileApprovalDefaultsLabel": "默认审批"')
  assertIncludes(zh, '"configFileApprovalDefaultInherited": "使用工具默认"')
  assertIncludes(zh, '"contextWindowCharLimit": "上下文预算"')
  assertIncludes(zh, '"duplicateConfigFile": "复制"')
  assertIncludes(zh, '"copyConfigFile": "复制 JSON"')
  assertIncludes(zh, '"downloadConfigFile": "下载 JSON"')
  assertIncludes(zh, '"importConfigFile": "载入配置文件"')
  assertIncludes(zh, '"configFileImported": "已从 {{fileName}} 导入并激活配置文件')
  assertIncludes(zh, '"deleteConfigFile": "删除"')
  assertIncludes(zh, '"restoreConfigFileBackup": "恢复上一份配置文件"')
  assertIncludes(zh, '"configFileBackupHelp": "已保留上一份配置文件快照')
  assertIncludes(zh, '"configFileRollbackRestored": "已恢复上一份配置文件快照')
  assertIncludes(zh, '"configFileDiffTitle": "切换影响预览"')
  assertIncludes(zh, '"configFileSwitchResetsToolPermissions"')
  assertIncludes(zh, '"current": "当前配置"')
  assertIncludes(en, '"configFilesPanel": "Config Files"')
  assertIncludes(en, '"installedCapabilitiesPanel": "Installed Capabilities"')
  assertIncludes(en, '"activeConfigFileLabel": "Selected config file"')
  assertIncludes(en, '"saveConfigFile": "Set active config file"')
  assertIncludes(en, '"configFileSaved": "Currently active"')
  assertNotIncludes(en, 'defaultConfigFileLabel')
  assertIncludes(en, '"configFileScopeHelp": "A config file is only the provider-session config for future new runs, not the Agent identity')
  assertIncludes(en, '"saveConfigFileDetails": "Save details"')
  assertIncludes(en, '"configFileLimitsLabel": "Provider session limits"')
  assertIncludes(en, '"configFileApprovalDefaultsLabel": "Default approval"')
  assertIncludes(en, '"configFileApprovalDefaultInherited": "Use tool default"')
  assertIncludes(en, '"contextWindowCharLimit": "Context budget"')
  assertIncludes(en, '"duplicateConfigFile": "Duplicate"')
  assertIncludes(en, '"copyConfigFile": "Copy JSON"')
  assertIncludes(en, '"downloadConfigFile": "Download JSON"')
  assertIncludes(en, '"importConfigFile": "Load Config File"')
  assertIncludes(en, '"configFileImported": "Imported and activated config file from {{fileName}}')
  assertIncludes(en, '"deleteConfigFile": "Delete"')
  assertIncludes(en, '"restoreConfigFileBackup": "Restore Previous Config File"')
  assertIncludes(en, '"configFileBackupHelp": "Kept the previous config file snapshot')
  assertIncludes(en, '"configFileRollbackRestored": "Restored previous config file snapshot')
  assertIncludes(en, '"configFileDiffTitle": "Switch Impact Preview"')
  assertIncludes(zh, '"settingsSnapshotSkillsTargetMissing": "快照包含 Skill 启用或 instruction')
  assertIncludes(en, '"settingsSnapshotSkillsTargetMissing": "The snapshot includes Skill enablement or instruction')
  assertIncludes(zh, 'Skill 启用选择与 instruction')
  assertIncludes(zh, '导入时会把 {{count}} 条 Skill 启用选择写入目标配置文件，并保存对应 instruction。')
  assertIncludes(en, 'Skill enablement and instruction')
  assertIncludes(en, 'Import writes {{count}} Skill enablement choices into the target config file and saves matching instruction.')
  assertIncludes(en, '"configFileSwitchResetsToolPermissions"')
  assertIncludes(en, '"current": "Current config"')
})

test('agent settings exposes tool permissions controls', () => {
  const source = readFileSync(resolve('src/features/agent/components/AIAgentSettingsPage.tsx'), 'utf8')
  const clientSource = readProviderSessionClientContractSource()
  const protocolSource = readFileSync(resolve('../../packages/core/src/agent/protocol.ts'), 'utf8')
  const zh = readFileSync(resolve('src/i18n/locales/zh-CN.json'), 'utf8')
  const en = readFileSync(resolve('src/i18n/locales/en-US.json'), 'utf8')

  assertIncludes(source, "queryKey: ['agent-settings-tool-permissions'")
  assertIncludes(source, 'settingsProviderSessionClient.getCapabilities()')
  assertIncludes(source, 'buildToolStats')
  assertIncludes(source, "tool.source === 'local'")
  assertNotIncludes(source, 'buildToolManagementItems')
  assertIncludes(source, 'tool.runtime?.approvalRequired')
  assertIncludes(source, 'tool.runtime?.grantMode')
  assertIncludes(source, 'tool.runtime?.execution')
  assertIncludes(source, 'tool.runtime?.reason')
  assertNotIncludes(source, 'testId="agent-settings-tool-management-map"')
  assertNotIncludes(source, 'testId="agent-settings-tool-management-view"')
  assertNotIncludes(source, "onChange={(view) => selectManagementView('tools', view)}")
  assertNotIncludes(source, "selectManagementView('tools', 'config')")
  assertNotIncludes(source, "toolManagementView === 'catalog'")
  assertNotIncludes(source, "toolManagementView === 'config'")
  assertNotIncludes(source, "toolManagementView === 'runtime'")
  assertNotIncludes(source, 'data-testid="agent-settings-tool-catalog-section"')
  assertNotIncludes(source, 'data-testid="agent-settings-tool-config-section"')
  assertNotIncludes(source, 'data-testid="agent-settings-tool-runtime-section"')
  assertNotIncludes(source, 'data-testid="agent-settings-tool-runtime-list"')
  assertNotIncludes(source, 'const runtimeTools = useMemo')
  assertNotIncludes(source, 'toolRuntimeRank')
  assertNotIncludes(source, 'agents.settings.toolRuntimeListPanel')
  assertNotIncludes(source, 'agents.settings.toolRuntimeListHelp')
  assertNotIncludes(source, 'agents.settings.toolManagementDetails.runtime')
  assertIncludes(zh, '"local": "本地"')
  assertIncludes(en, '"local": "Local"')
  assertNotIncludes(source, 'agents.settings.toolManagementLayerHelp.catalog')
  assertNotIncludes(source, 'agents.settings.toolManagementLayerHelp.config')
  assertNotIncludes(source, 'agents.settings.toolManagementLayerHelp.runtime')
  assertNotIncludes(source, 'agents.settings.toolPermissionsFields.runtimeAllow')
  assertIncludes(source, 'availableWriteRisk')
  assertIncludes(source, 'buildToolGrantWorkspaces')
  assertIncludes(source, 'buildToolPermissionsWorkspaceIssues')
  assertIncludes(source, 'toolPermissionsWorkspaceIssues.length > 0')
  assertIncludes(source, 'toolPermissionsHasWorkspaceIssues')
  assertIncludes(source, "agents.settings.readinessDetails.toolsInvalid")
  assertIncludes(source, "id: 'tool-permissions-invalid'")
  assertIncludes(source, 'function formatSettingsToolPermissionsIssue')
  assertIncludes(source, "quickFix === 'fix-tool-permissions-workspace-issues'")
  assertIncludes(source, 'function fixToolPermissionsWorkspaceIssues')
  assertIncludes(source, "fixToolPermissionsWorkspaceIssues({ audit: true })")
  assertIncludes(source, "if (options?.audit) recordSettingsQuickFix('tools', 'agents.settings.fixToolPermissionsWorkspaceIssues', 'workspace_repair')")
  assertIncludes(source, 'agents.settings.actionItemReasons.toolNotConfigFileGranted')
  assertIncludes(source, 'agents.settings.actionItemReasons.toolUnavailableAllow')
  assertNotIncludes(source, 'data-testid="agent-settings-fix-tool-permissions-workspace-issues"')
  assertIncludes(source, "issue.reasonKey === 'agents.settings.toolPermissionsWorkspaceIssueDetails.notConfigFileGranted'")
  assertIncludes(source, "issue.reasonKey === 'agents.settings.toolPermissionsWorkspaceIssueDetails.unavailableAllow'")
  assertIncludes(source, 'data-testid="agent-settings-tool-permissions-workspace-issues"')
  assertIncludes(source, 'agents.settings.toolPermissionsWorkspaceInvalid')
  assertIncludes(source, 'agents.settings.toolPermissionsWorkspaceIssueDetails.notConfigFileGranted')
  assertIncludes(source, 'agents.settings.toolPermissionsWorkspaceIssueDetails.unavailableAllow')
  assertIncludes(source, 'function ToolPermissionsRow')
  assertIncludes(source, 'const canAllow = !readOnly && tool.available && configFileGranted')
  assertIncludes(source, '<SelectItem value="allow" disabled={!canAllow}>')
  assertIncludes(source, 'agents.settings.toolPermissionsAllowDisabled')
  assertIncludes(source, 'requiresApproval')
  assertIncludes(source, 'projectScoped')
  assertIncludes(source, 'currentToolGrants.has(tool.name)')
  assertNotIncludes(source, 'providerSessionClient.saveConfigFileToolPermissions')
  assertIncludes(source, 'settingsProviderSessionClient.saveProviderConfigFile')
  assertIncludes(source, 'toolPermissionsAuditSummaryValues(toolGrantWorkspaces)')
  assertIncludes(source, 'function toolPermissionsAuditSummaryValues')
  assertIncludes(source, "grant.mode === 'allow'")
  assertIncludes(source, "grant.mode === 'deny'")
  assertIncludes(source, "(grant.approval ?? 'never') !== 'never'")
  assertNotIncludes(source, legacyAgentTerm("id: 'auto-", 'permission-write-risk', "'"))
  assertNotIncludes(source, legacyAgentTerm('runtime', 'Limit', 'SetHasAutoWriteRisk'))
  assertNotIncludes(source, legacyAgentTerm("quickFix === 'downgrade-", 'auto-permission', "'"))
  assertNotIncludes(source, legacyAgentTerm("persistHintKey: 'agents.settings.actionItemPersistHints.", 'localDefaults', 'Immediately', "'"))
  assertNotIncludes(source, legacyAgentTerm('agents.settings.actionItems.', 'autoPermission', 'WriteRisk'))
  assertNotIncludes(source, legacyAgentTerm('agents.settings.quickFixes.', 'downgrade', 'AutoPermission'))
  assertIncludes(source, 'toolPermissionsEditHelp')
  assertIncludes(protocolSource, 'resolvedTools: ResolvedToolCatalog')
  assertIncludes(clientSource, "saveConfigFileToolPermissions(input: { configFileId: string; toolGrants: ProviderManifest['tools'] }")
  assertIncludes(protocolSource, "export type ProviderToolRiskLevel = 'read' | 'workspace' | 'write' | 'generate' | 'destructive' | 'ui'")
  assertIncludes(protocolSource, "export type ProviderToolApprovalMode = 'never' | 'always' | 'on_write'")
  assertIncludes(protocolSource, "export type ProviderToolGrantMode = 'allow' | 'deny'")
  assertIncludes(protocolSource, 'approval: ProviderToolApprovalMode')
  assertIncludes(protocolSource, 'export interface ProviderToolExecutionMetadata')
  assertIncludes(protocolSource, 'export interface ProviderToolRuntimeExplanation')
  assertIncludes(protocolSource, 'runtime?: ProviderToolRuntimeExplanation')
  assertNotIncludes(protocolSource, 'export type AgentToolRiskLevel')
  assertNotIncludes(protocolSource, 'export type AgentToolApprovalMode')
  assertNotIncludes(protocolSource, 'export type AgentToolGrantMode')
  assertNotIncludes(protocolSource, 'export interface AgentToolExecutionMetadata')
  assertNotIncludes(protocolSource, 'export interface AgentToolRuntimeExplanation')
  assertIncludes(zh, '"toolPermissionsPanel": "工具权限"')
  assertNotIncludes(zh, '"toolManagementMapPanel": "Tool 管理地图"')
  assertNotIncludes(zh, '"toolManagementLayerHelp"')
  assertNotIncludes(zh, '"toolRuntimeListPanel": "运行时 Tool 清单"')
  assertNotIncludes(zh, '"toolRuntimeListHelp": "只读展示当前运行时实际看到的工具')
  assertNotIncludes(zh, '"config": "{{grants}} 条配置文件授权')
  assertIncludes(zh, '"grantMode": "Provider 授权"')
  assertIncludes(zh, '"approvalReason": "审批原因"')
  assertIncludes(zh, '"runtimeAllow": "Provider 允许"')
  assertIncludes(zh, '"on_write": "写入风险审批"')
  assertNotIncludes(zh, '"runtime": "{{available}} 个可执行')
  assertIncludes(zh, '"saveToolPermissions": "保存工具权限"')
  assertIncludes(zh, '"toolPermissionsSaved": "已保存工具权限：{{count}} 条规则（允许 {{allow}} / 拒绝 {{deny}} / 审批 {{approvals}}）"')
  assertIncludes(zh, '"toolPermissionsAllowDisabled": "只有当前配置文件授权且 Provider 会话可用的工具才能保存为允许。"')
  assertIncludes(zh, '"toolPermissionsWorkspaceIssues": "工具权限工作区存在不可保存项"')
  assertIncludes(zh, '"toolPermissionsInvalid": "工具权限工作区不可保存"')
  assertIncludes(zh, '"toolsInvalid": "工具权限工作区有 {{count}} 个不可保存项。"')
  assertIncludes(zh, '"fixToolPermissionsWorkspaceIssues": "修复工作区"')
  assertIncludes(zh, '"notConfigFileGranted": "不在当前配置文件授权范围内。"')
  assertIncludes(zh, '"unavailableAllow": "当前不可用（{{reason}}），不能保存为允许。"')
  assertIncludes(zh, '"toolNotConfigFileGranted": "{{toolName}} 不在当前配置文件授权范围内"')
  assertIncludes(zh, '"toolUnavailableAllow": "{{toolName}} 当前不可用（{{reason}}），不能保存为允许"')
  assertIncludes(zh, '"requiresApproval": "需审批"')
  assertNotIncludes(zh, legacyAgentTerm('autoPermission', 'WriteRisk'))
  assertNotIncludes(zh, legacyAgentTerm('downgrade', 'AutoPermission'))
  assertNotIncludes(zh, legacyAgentTerm('localDefaults', 'Immediately'))
  assertNotIncludes(zh, legacyAgentTerm('runtime', 'Limit', 'Set'))
  assertIncludes(en, '"toolPermissionsPanel": "Tool Permissions"')
  assertNotIncludes(en, '"toolManagementMapPanel": "Tool management map"')
  assertNotIncludes(en, '"toolManagementLayerHelp"')
  assertNotIncludes(en, '"toolRuntimeListPanel": "Runtime tool list"')
  assertNotIncludes(en, '"toolRuntimeListHelp": "Read-only view of tools the runtime currently sees')
  assertNotIncludes(en, '"config": "{{grants}} config-file grant')
  assertIncludes(en, '"grantMode": "Provider grant"')
  assertIncludes(en, '"approvalReason": "Approval reason"')
  assertIncludes(en, '"runtimeAllow": "Provider allow"')
  assertIncludes(en, '"on_write": "Write-risk approval"')
  assertNotIncludes(en, '"runtime": "{{available}} executable')
  assertIncludes(en, '"saveToolPermissions": "Save Tool Permissions"')
  assertIncludes(en, '"toolPermissionsSaved": "Saved tool permissions: {{count}} rules (allow {{allow}} / deny {{deny}} / approval {{approvals}})"')
  assertIncludes(en, '"toolPermissionsAllowDisabled": "Only tools granted by the current config file and available in the provider session can be saved as allowed."')
  assertIncludes(en, '"toolPermissionsWorkspaceIssues": "Tool permission workspace has unsavable items"')
  assertIncludes(en, '"toolPermissionsInvalid": "Tool permission workspace cannot be saved"')
  assertIncludes(en, '"toolsInvalid": "Tool permission workspace has {{count}} unsavable items."')
  assertIncludes(en, '"fixToolPermissionsWorkspaceIssues": "Fix Workspace"')
  assertIncludes(en, '"notConfigFileGranted": "Not granted by the current config file."')
  assertIncludes(en, '"unavailableAllow": "Currently unavailable ({{reason}}), so it cannot be saved as allowed."')
  assertIncludes(en, '"toolNotConfigFileGranted": "{{toolName}} is not granted by the current config file"')
  assertIncludes(en, '"toolUnavailableAllow": "{{toolName}} is currently unavailable ({{reason}}), so it cannot be saved as allowed"')
  assertIncludes(en, '"requiresApproval": "Needs approval"')
  assertNotIncludes(en, legacyAgentTerm('autoPermission', 'WriteRisk'))
  assertNotIncludes(en, legacyAgentTerm('downgrade', 'AutoPermission'))
  assertNotIncludes(en, legacyAgentTerm('localDefaults', 'Immediately'))
  assertNotIncludes(en, legacyAgentTerm('runtime', 'Limit', 'Set'))
})

test('agent console removes the standalone advanced diagnostics page', () => {
  const appSource = readFileSync(resolve('src/App.tsx'), 'utf8')
  const routesSource = readFileSync(resolve('src/routes/projectRoutes.ts'), 'utf8')
  const consoleSource = readFileSync(resolve('src/features/agent/components/AgentConsolePage.tsx'), 'utf8')
  const consoleNavSource = readFileSync(resolve('src/features/agent/components/AgentConsoleNav.tsx'), 'utf8')
  const settingsSource = readFileSync(resolve('src/features/agent/components/AIAgentSettingsPage.tsx'), 'utf8')
  const zh = readFileSync(resolve('src/i18n/locales/zh-CN.json'), 'utf8')
  const en = readFileSync(resolve('src/i18n/locales/en-US.json'), 'utf8')

  assert.equal(existsSync(resolve('src/features/agent/components/AIAgentDebugPage.tsx')), false)
  assert.equal(existsSync(resolve('src/pages/agent/AIAgentDebugPage.tsx')), false)
  assert.equal(existsSync(resolve('../../contracts/agent/agent-debug-bundle-v1.schema.json')), false)
  assert.equal(existsSync(resolve('../../contracts/agent/agent-debug-bundle-v1.fixture.json')), false)
  assertNotIncludes(routesSource, 'agentDebug')
  assertNotIncludes(appSource, 'AIAgentDebugPage')
  assertNotIncludes(appSource, 'ROUTES.agentDebug')
  assertNotIncludes(consoleSource, 'ROUTES.agentDebug')
  assertNotIncludes(consoleSource, '高级诊断')
  assertNotIncludes(consoleNavSource, '高级诊断')
  assertNotIncludes(settingsSource, 'agent-settings-open-debug')
  assertNotIncludes(settingsSource, 'ROUTES.agentDebug')
  assertNotIncludes(zh, '"agentDebug"')
  assertNotIncludes(zh, '"openDebug"')
  assertNotIncludes(en, '"agentDebug"')
  assertNotIncludes(en, '"openDebug"')
})

test('agent tool diagnostics expose the resolved permission chain', () => {
  const clientSource = readProviderSessionClientContractSource()
  const storeSource = readFileSync(resolve('src/features/agent/state/agentStore.ts'), 'utf8')
  const protocolSource = readFileSync(resolve('../../packages/core/src/agent/protocol.ts'), 'utf8')
  const debugPreviewSource = readFileSync(resolve('src/features/agent/components/AgentDebugPreviewDialog.tsx'), 'utf8')
  const contextDiagnosticSource = readFileSync(resolve('src/features/agent/components/ContextDiagnosticCard.tsx'), 'utf8')
  const zh = readFileSync(resolve('src/i18n/locales/zh-CN.json'), 'utf8')
  const en = readFileSync(resolve('src/i18n/locales/en-US.json'), 'utf8')

  assertIncludes(protocolSource, 'resolution?: {')
  assertIncludes(protocolSource, "grantSource: 'manifest' | 'skill' | 'none'")
  assertIncludes(protocolSource, 'grantingSkillIds?: string[]')
  assertIncludes(protocolSource, 'activeSkillIds: string[]')
  assertIncludes(storeSource, 'export type ChatContextDiagnosticTool = AgentContextDiagnosticTool')
  assertIncludes(debugPreviewSource, 'function toolResolutionLabel(tool: ProviderToolDescriptor')
  assertIncludes(debugPreviewSource, 'resolution.authorized')
  assertIncludes(debugPreviewSource, 'resolution.visible')
  assertIncludes(debugPreviewSource, 'resolution.grantSource')
  assertIncludes(debugPreviewSource, 'resolution.activeSkillIds.length')
  assertIncludes(contextDiagnosticSource, 'function contextToolResolutionLabel')
  assertIncludes(contextDiagnosticSource, 'tool.resolution')
  assertIncludes(contextDiagnosticSource, 'resolution.activeSkillIds.length')
  assertIncludes(zh, '"activeSkills": "激活技能"')
  assertIncludes(en, '"activeSkills": "Active skills"')
})

function assertIncludes(value: string, expected: string) {
  assert.equal(value.includes(expected), true, `expected output to include ${expected}`)
}

function assertNotIncludes(value: string, unexpected: string) {
  assert.equal(value.includes(unexpected), false, `expected output not to include ${unexpected}`)
}

function legacyAgentTerm(...parts: string[]) {
  return parts.join('')
}

function assertOrdered(value: string, expected: string[]) {
  let previousIndex = -1
  for (const item of expected) {
    const index = value.indexOf(item)
    assert.notEqual(index, -1, `expected output to include ${item}`)
    assert.ok(index > previousIndex, `expected ${item} to appear after the previous item`)
    previousIndex = index
  }
}
