import test from 'node:test'; import assert from 'node:assert/strict'; import { buildManagedAgentState, getInitialManagedAgentPaths, updateCodebuddyManagedEnv, updateCodebuddyManagedOptions, } from '../settings/tabs/ai/managedAgentState'; import type { ExternalAgentConfig } from '../../infrastructure/ai/types'; test('buildManagedAgentState removes stale managed agents when path detection fails', () => { const agents: ExternalAgentConfig[] = [ { id: 'discovered_codex', name: 'Codex CLI', command: '/usr/local/bin/codex', enabled: true, sdkBackend: 'codex', }, { id: 'custom-agent', name: 'Custom Agent', command: '/usr/local/bin/custom-agent', enabled: true, }, ]; const state = buildManagedAgentState( agents, 'discovered_codex', 'codex', { path: '/usr/local/bin/codex', version: null, available: false }, ); assert.deepEqual( state.agents.map((agent) => agent.id), ['custom-agent'], ); assert.equal(state.defaultAgentId, 'catty'); }); test('buildManagedAgentState keeps unrelated defaults when removing stale managed agents', () => { const agents: ExternalAgentConfig[] = [ { id: 'discovered_claude', name: 'Claude Code', command: '/usr/local/bin/claude', enabled: true, sdkBackend: 'claude', }, { id: 'custom-agent', name: 'Custom Agent', command: '/usr/local/bin/custom-agent', enabled: true, }, ]; const state = buildManagedAgentState( agents, 'custom-agent', 'claude', { path: '/usr/local/bin/claude', version: null, available: false }, ); assert.deepEqual( state.agents.map((agent) => agent.id), ['custom-agent'], ); assert.equal(state.defaultAgentId, 'custom-agent'); }); test('buildManagedAgentState stores the system Claude executable for SDK runs', () => { const state = buildManagedAgentState( [], 'catty', 'claude', { path: '/opt/homebrew/bin/claude', version: '2.1.145 (Claude Code)', available: true }, ); assert.equal(state.agents.length, 1); assert.equal(state.agents[0].command, '/opt/homebrew/bin/claude'); assert.equal(state.agents[0].sdkBackend, 'claude'); assert.deepEqual(state.agents[0].env, { CLAUDE_CODE_EXECUTABLE: '/opt/homebrew/bin/claude', }); }); test('buildManagedAgentState stores SDK backend keys for discovered managed agents', () => { const codexState = buildManagedAgentState( [], 'catty', 'codex', { path: '/opt/homebrew/bin/codex', version: '1.0.0', available: true }, ); const copilotState = buildManagedAgentState( [], 'catty', 'copilot', { path: '/opt/homebrew/bin/copilot', version: '1.0.0', available: true }, ); assert.equal(codexState.agents[0].sdkBackend, 'codex'); assert.equal(codexState.agents[0].cliVersion, '1.0.0'); assert.equal(copilotState.agents[0].sdkBackend, 'copilot'); assert.equal(copilotState.agents[0].acpArgs, undefined); }); test('buildManagedAgentState preserves the experimental Codex runtime across path refreshes', () => { const state = buildManagedAgentState( [{ id: 'discovered_codex', name: 'Codex CLI', command: '/old/codex', enabled: true, sdkBackend: 'codex', codexRuntime: 'app-server', }], 'discovered_codex', 'codex', { path: '/new/codex', version: '0.144.3', available: true }, ); assert.equal(state.agents[0].codexRuntime, 'app-server'); assert.equal(state.agents[0].command, '/new/codex'); }); test('buildManagedAgentState preserves Grok runtime across path refreshes', () => { const state = buildManagedAgentState( [{ id: 'discovered_grok', name: 'Grok Build', command: '/old/grok', enabled: true, sdkBackend: 'grok', grokRuntime: 'streaming-json', }], 'discovered_grok', 'grok', { path: '/new/grok', version: '0.2.118', available: true }, ); assert.equal(state.agents[0].grokRuntime, 'streaming-json'); assert.equal(state.agents[0].command, '/new/grok'); assert.equal(state.agents[0].sdkBackend, 'grok'); }); test('getInitialManagedAgentPaths ignores auto-detected command paths', () => { const state = buildManagedAgentState( [], 'catty', 'codex', { path: '/opt/homebrew/bin/codex', version: '1.0.0', available: true }, 'auto', ); assert.equal(state.agents[0].commandSource, 'auto'); assert.equal(getInitialManagedAgentPaths(state.agents).codex, ''); }); test('getInitialManagedAgentPaths keeps manual and legacy command paths', () => { const manualState = buildManagedAgentState( [], 'catty', 'codex', { path: '/opt/homebrew/bin/codex', version: '1.0.0', available: true }, 'manual', ); assert.equal(getInitialManagedAgentPaths(manualState.agents).codex, '/opt/homebrew/bin/codex'); assert.equal(getInitialManagedAgentPaths([{ id: 'discovered_codex', name: 'Codex CLI', command: '/legacy/bin/codex', enabled: true, available: true, sdkBackend: 'codex', }]).codex, '/legacy/bin/codex'); }); test('buildManagedAgentState stores SDK backend key for discovered Cursor', () => { const state = buildManagedAgentState( [], 'catty', 'cursor', { path: 'cursor', version: 'Cursor SDK 1.0.18', available: true }, ); assert.equal(state.agents[0].id, 'discovered_cursor'); assert.equal(state.agents[0].name, 'Cursor'); assert.equal(state.agents[0].command, 'cursor'); assert.equal(state.agents[0].sdkBackend, 'cursor'); }); test('buildManagedAgentState preserves a saved Cursor API key when SDK is not ready', () => { const agents: ExternalAgentConfig[] = [ { id: 'discovered_cursor', name: 'Cursor', command: 'cursor', enabled: true, available: true, sdkBackend: 'cursor', apiKey: 'enc:v1:test', }, ]; const state = buildManagedAgentState( agents, 'discovered_cursor', 'cursor', { path: 'cursor', version: 'Cursor SDK', available: false, installed: true }, ); assert.equal(state.agents[0].id, 'discovered_cursor'); assert.equal(state.agents[0].apiKey, 'enc:v1:test'); // Keep enabled so a later mode/path that becomes available is not sticky-disabled. assert.equal(state.agents[0].enabled, true); assert.equal(state.agents[0].available, false); assert.equal(state.defaultAgentId, 'catty'); }); test('buildManagedAgentState preserves enabled when CLI login probe is temporarily unavailable', () => { const agents: ExternalAgentConfig[] = [ { id: 'discovered_cursor', name: 'Cursor', command: '/bin/cursor-agent', enabled: true, available: true, sdkBackend: 'cursor', cursorAuthMode: 'cli-login', apiKey: 'enc:v1:test', }, ]; const unavailable = buildManagedAgentState( agents, 'discovered_cursor', 'cursor', { path: 'cursor', version: 'Cursor Agent CLI', available: false, installed: true, cliLoginOk: false, apiKeyOk: true, sdkInstalled: true, }, ); assert.equal(unavailable.agents[0].enabled, true); assert.equal(unavailable.agents[0].available, false); assert.equal(unavailable.agents[0].apiKey, 'enc:v1:test'); assert.equal(unavailable.agents[0].cursorAuthMode, 'cli-login'); // When CLI login returns, mode-aware available becomes true and enabled stays on. const recovered = buildManagedAgentState( unavailable.agents, 'catty', 'cursor', { path: '/bin/cursor-agent', cliBinPath: '/bin/cursor-agent', version: 'Cursor Agent CLI', available: true, installed: true, cliLoginOk: true, apiKeyOk: true, sdkInstalled: true, authSource: 'cli-login', }, ); assert.equal(recovered.agents[0].enabled, true); assert.equal(recovered.agents[0].available, true); assert.equal(recovered.agents[0].cursorAuthMode, 'cli-login'); assert.equal(recovered.agents[0].command, '/bin/cursor-agent'); }); test('buildManagedAgentState keeps API key when Cursor stays on CLI login mode', () => { const agents: ExternalAgentConfig[] = [ { id: 'discovered_cursor', name: 'Cursor', command: '/bin/cursor-agent', enabled: true, available: true, sdkBackend: 'cursor', cursorAuthMode: 'cli-login', apiKey: 'enc:v1:keep-me', }, ]; const state = buildManagedAgentState( agents, 'discovered_cursor', 'cursor', { path: '/bin/cursor-agent', cliBinPath: '/bin/cursor-agent', version: 'Cursor Agent CLI', available: true, installed: true, cliLoginOk: true, apiKeyOk: true, sdkInstalled: true, authSource: 'cli-login', }, ); assert.equal(state.agents[0].apiKey, 'enc:v1:keep-me'); assert.equal(state.agents[0].cursorAuthMode, 'cli-login'); }); test('buildManagedAgentState stores CODEBUDDY_CODE_PATH for codebuddy', () => { const state = buildManagedAgentState( [], 'catty', 'codebuddy', { path: '/opt/homebrew/bin/codebuddy', version: '0.1.0', available: true }, ); assert.equal(state.agents.length, 1); assert.equal(state.agents[0].command, '/opt/homebrew/bin/codebuddy'); assert.equal(state.agents[0].sdkBackend, 'codebuddy'); assert.deepEqual(state.agents[0].env, { CODEBUDDY_CODE_PATH: '/opt/homebrew/bin/codebuddy', }); }); test('buildManagedAgentState stores OPENCODE_BIN for opencode', () => { const state = buildManagedAgentState( [], 'catty', 'opencode', { path: '/opt/homebrew/bin/opencode', version: '1.0.0', available: true }, ); assert.equal(state.agents.length, 1); assert.equal(state.agents[0].id, 'discovered_opencode'); assert.equal(state.agents[0].command, '/opt/homebrew/bin/opencode'); assert.equal(state.agents[0].sdkBackend, 'opencode'); assert.deepEqual(state.agents[0].env, { OPENCODE_BIN: '/opt/homebrew/bin/opencode', }); }); test('updateCodebuddyManagedEnv creates a disabled managed entry before CLI detection', () => { const state = updateCodebuddyManagedEnv([], 'internal', 'CODEBUDDY_API_KEY=secret'); assert.equal(state.length, 1); assert.equal(state[0].id, 'discovered_codebuddy'); assert.equal(state[0].command, 'codebuddy'); assert.equal(state[0].enabled, false); assert.deepEqual(state[0].env, { CODEBUDDY_INTERNET_ENVIRONMENT: 'internal', CODEBUDDY_API_KEY: 'secret', }); }); test('buildManagedAgentState preserves disabled CodeBuddy config when path detection fails', () => { const agents = updateCodebuddyManagedEnv([], 'ioa', 'CODEBUDDY_AUTH_TOKEN=token'); const state = buildManagedAgentState( agents, 'discovered_codebuddy', 'codebuddy', { path: null, version: null, available: false }, ); assert.equal(state.defaultAgentId, 'catty'); assert.equal(state.agents.length, 1); assert.equal(state.agents[0].id, 'discovered_codebuddy'); assert.equal(state.agents[0].enabled, false); assert.deepEqual(state.agents[0].env, { CODEBUDDY_INTERNET_ENVIRONMENT: 'ioa', CODEBUDDY_AUTH_TOKEN: 'token', }); }); test('buildManagedAgentState enables preconfigured CodeBuddy when path detection succeeds', () => { const agents = updateCodebuddyManagedEnv([], 'internal', 'CODEBUDDY_API_KEY=secret'); const state = buildManagedAgentState( agents, 'catty', 'codebuddy', { path: '/opt/homebrew/bin/codebuddy', version: '0.1.0', available: true }, ); assert.equal(state.agents.length, 1); assert.equal(state.agents[0].enabled, true); assert.equal(state.agents[0].command, '/opt/homebrew/bin/codebuddy'); assert.deepEqual(state.agents[0].env, { CODEBUDDY_INTERNET_ENVIRONMENT: 'internal', CODEBUDDY_API_KEY: 'secret', CODEBUDDY_CODE_PATH: '/opt/homebrew/bin/codebuddy', }); }); test('updateCodebuddyManagedEnv removes an empty pre-detection placeholder', () => { const agents = updateCodebuddyManagedEnv([], 'internal', 'CODEBUDDY_API_KEY=secret'); const cleared = updateCodebuddyManagedEnv(agents, '', ''); assert.deepEqual(cleared, []); }); test('updateCodebuddyManagedOptions persists settings before CLI detection', () => { const state = updateCodebuddyManagedOptions([], { effort: 'high', enableFileCheckpointing: true, }); assert.equal(state.length, 1); assert.equal(state[0].id, 'discovered_codebuddy'); assert.equal(state[0].command, 'codebuddy'); assert.equal(state[0].enabled, false); assert.deepEqual(state[0].codebuddyOptions, { effort: 'high', enableFileCheckpointing: true, }); }); test('buildManagedAgentState preserves advanced CodeBuddy config when detection fails', () => { const agents = updateCodebuddyManagedOptions([], { effort: 'high' }); const state = buildManagedAgentState( agents, 'discovered_codebuddy', 'codebuddy', { path: null, version: null, available: false }, ); assert.equal(state.defaultAgentId, 'catty'); assert.equal(state.agents.length, 1); assert.equal(state.agents[0].enabled, false); assert.equal(state.agents[0].available, false); assert.deepEqual(state.agents[0].codebuddyOptions, { effort: 'high' }); }); test('updateCodebuddyManagedOptions removes an empty pre-detection placeholder', () => { const agents = updateCodebuddyManagedOptions([], { effort: 'high' }); const cleared = updateCodebuddyManagedOptions(agents, undefined); assert.deepEqual(cleared, []); }); test('buildManagedAgentState does not remove user-created matching agents', () => { const agents: ExternalAgentConfig[] = [ { id: 'my-claude-wrapper', name: 'My Claude Wrapper', command: '/usr/local/bin/claude', enabled: true, sdkBackend: 'claude', }, ]; const state = buildManagedAgentState( agents, 'my-claude-wrapper', 'claude', { path: '/usr/local/bin/claude', version: null, available: false }, ); assert.deepEqual(state.agents, agents); assert.equal(state.defaultAgentId, 'my-claude-wrapper'); }); test('buildManagedAgentState only rewrites settings-managed discovered agents', () => { const agents: ExternalAgentConfig[] = [ { id: 'my-codex-wrapper', name: 'My Codex Wrapper', command: '/usr/local/bin/codex', enabled: true, sdkBackend: 'codex', }, ]; const state = buildManagedAgentState( agents, 'my-codex-wrapper', 'codex', { path: '/opt/netcatty/codex', version: 'Bundled legacy adapter', available: true }, ); assert.deepEqual( state.agents.map((agent) => agent.id), ['my-codex-wrapper', 'discovered_codex'], ); assert.equal(state.agents[0], agents[0]); assert.equal(state.defaultAgentId, 'my-codex-wrapper'); });