"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const node_test_1 = require("node:test");
const strict_1 = __importDefault(require("node:assert/strict"));
const node_test_2 = require("node:test");
const logger_1 = require("../logger");
const antigravity_1 = require("../ai/antigravity");
const anthropicSdk_1 = require("../ai/anthropicSdk");
const claude_1 = require("../ai/claude");
const codex_1 = require("../ai/codex");
const cursor_1 = require("../ai/cursor");
const windsurf_1 = require("../ai/windsurf");
const windsurf_2 = require("../ai/windsurf");
const index_1 = require("../ai/index");
const platform_1 = require("../platform");
// eslint-disable-next-line @typescript-eslint/no-require-imports
const childProcess = require('node:child_process');
// ─── Helpers ──────────────────────────────────────────────────────────────────
function mockSpawnSync(overrides) {
    return node_test_2.mock.method(childProcess, 'spawnSync', () => ({
        pid: 1,
        output: [],
        stdout: '',
        stderr: '',
        status: 0,
        signal: null,
        error: undefined,
        ...overrides,
    }));
}
function spyLogger() {
    return {
        info: node_test_2.mock.method(logger_1.logger, 'info', () => { }),
        warn: node_test_2.mock.method(logger_1.logger, 'warn', () => { }),
        debug: node_test_2.mock.method(logger_1.logger, 'debug', () => { }),
    };
}
// ─── ClaudeRunner ─────────────────────────────────────────────────────────────
(0, node_test_1.describe)('ClaudeRunner – failed tasks', () => {
    (0, node_test_1.beforeEach)(() => node_test_2.mock.restoreAll());
    (0, node_test_1.afterEach)(() => node_test_2.mock.restoreAll());
    (0, node_test_1.it)('returns success=false when claude exits with non-zero status', async () => {
        mockSpawnSync({ status: 1, stdout: '', stderr: 'something went wrong' });
        spyLogger();
        const runner = new claude_1.ClaudeRunner();
        const result = await runner.run('test prompt');
        strict_1.default.equal(result.success, false);
        strict_1.default.equal(result.error, 'something went wrong');
    });
    (0, node_test_1.it)('logs a warning with exit status on failure', async () => {
        mockSpawnSync({ status: 2, stdout: '', stderr: 'fatal error' });
        const spies = spyLogger();
        const runner = new claude_1.ClaudeRunner();
        await runner.run('test prompt');
        const warnCalls = spies.warn.mock.calls.map((c) => c.arguments[0]);
        strict_1.default.ok(warnCalls.some((msg) => msg?.includes('status 2')));
    });
    (0, node_test_1.it)('logs stderr on failure', async () => {
        mockSpawnSync({ status: 1, stdout: '', stderr: 'heap out of memory' });
        const spies = spyLogger();
        const runner = new claude_1.ClaudeRunner();
        await runner.run('test prompt');
        const warnCalls = spies.warn.mock.calls.map((c) => c.arguments[0]);
        strict_1.default.ok(warnCalls.some((msg) => msg?.includes('heap out of memory')));
    });
    (0, node_test_1.it)('logs spawn error when present', async () => {
        mockSpawnSync({ status: null, stdout: '', stderr: '', error: new Error('ENOENT') });
        const spies = spyLogger();
        const runner = new claude_1.ClaudeRunner();
        await runner.run('test prompt');
        const warnCalls = spies.warn.mock.calls.map((c) => c.arguments[0]);
        strict_1.default.ok(warnCalls.some((msg) => msg?.includes('ENOENT')));
    });
    (0, node_test_1.it)('returns empty output on failure', async () => {
        mockSpawnSync({ status: 1, stdout: '', stderr: 'err' });
        spyLogger();
        const runner = new claude_1.ClaudeRunner();
        const result = await runner.run('test prompt');
        strict_1.default.equal(result.output, '');
    });
});
// ─── ClaudeRunner – argv order ────────────────────────────────────────────────
(0, node_test_1.describe)('ClaudeRunner – argv order', () => {
    (0, node_test_1.beforeEach)(() => node_test_2.mock.restoreAll());
    (0, node_test_1.afterEach)(() => node_test_2.mock.restoreAll());
    (0, node_test_1.it)('first attempt passes --model opusplan (the default CLAUDE_MODEL)', async () => {
        const argvSnapshots = [];
        node_test_2.mock.method(childProcess, 'spawnSync', (cmd, args) => {
            // On Windows, spawnCommand calls findBin which may invoke where.exe —
            // skip those helper calls and only capture the real CLI invocations.
            const command = cmd;
            if (!command.endsWith('where.exe')) {
                argvSnapshots.push([...args]);
            }
            return {
                pid: 1,
                output: [],
                stdout: 'ok',
                stderr: '',
                status: 0,
                signal: null,
                error: undefined,
            };
        });
        spyLogger();
        const prev = process.env.CLAUDE_MODEL;
        delete process.env.CLAUDE_MODEL;
        try {
            await new claude_1.ClaudeRunner().run('fix the bug');
        }
        finally {
            if (prev === undefined)
                delete process.env.CLAUDE_MODEL;
            else
                process.env.CLAUDE_MODEL = prev;
        }
        strict_1.default.equal(argvSnapshots.length, 1);
        const args = argvSnapshots[0];
        const pIdx = args.indexOf('-p');
        strict_1.default.ok(pIdx >= 0, 'expected -p in spawn argv (after any Windows node cli.js prefix)');
        strict_1.default.equal(args[pIdx + 1], 'fix the bug');
        strict_1.default.ok(args.includes('--dangerously-skip-permissions'));
        const modelIdx = args.indexOf('--model');
        strict_1.default.ok(modelIdx >= 0, 'expected --model in spawn argv');
        strict_1.default.equal(args[modelIdx + 1], 'opusplan');
    });
    (0, node_test_1.it)('uses CLAUDE_MODEL from env when set', async () => {
        const argvSnapshots = [];
        node_test_2.mock.method(childProcess, 'spawnSync', (cmd, args) => {
            const command = cmd;
            if (!command.endsWith('where.exe')) {
                argvSnapshots.push([...args]);
            }
            return {
                pid: 1,
                output: [],
                stdout: 'ok',
                stderr: '',
                status: 0,
                signal: null,
                error: undefined,
            };
        });
        spyLogger();
        const prev = process.env.CLAUDE_MODEL;
        process.env.CLAUDE_MODEL = 'claude-sonnet-4-6';
        try {
            await new claude_1.ClaudeRunner().run('do it');
        }
        finally {
            if (prev === undefined)
                delete process.env.CLAUDE_MODEL;
            else
                process.env.CLAUDE_MODEL = prev;
        }
        const args = argvSnapshots[0];
        const modelIdx = args.indexOf('--model');
        strict_1.default.equal(args[modelIdx + 1], 'claude-sonnet-4-6');
    });
});
// ─── AntigravityRunner ────────────────────────────────────────────────────────
(0, node_test_1.describe)('AntigravityRunner', () => {
    (0, node_test_1.it)('isAvailable returns boolean (depends on agy/antigravity in PATH)', () => {
        const runner = new antigravity_1.AntigravityRunner();
        strict_1.default.equal(typeof runner.isAvailable(), 'boolean');
    });
});
// ─── CodexRunner ─────────────────────────────────────────────────────────────
(0, node_test_1.describe)('CodexRunner', () => {
    (0, node_test_1.it)('isAvailable returns boolean (depends on codex CLI in PATH)', () => {
        const runner = new codex_1.CodexRunner();
        strict_1.default.equal(typeof runner.isAvailable(), 'boolean');
    });
});
(0, node_test_1.describe)('AntigravityRunner – failed tasks', () => {
    (0, node_test_1.beforeEach)(() => node_test_2.mock.restoreAll());
    (0, node_test_1.afterEach)(() => node_test_2.mock.restoreAll());
    (0, node_test_1.it)('returns success=false when antigravity exits with non-zero status', async () => {
        mockSpawnSync({ status: 1, stdout: '', stderr: 'antigravity failed' });
        spyLogger();
        const runner = new antigravity_1.AntigravityRunner();
        const result = await runner.run('test prompt');
        strict_1.default.equal(result.success, false);
        strict_1.default.equal(result.error, 'antigravity failed');
    });
    (0, node_test_1.it)('logs a warning with exit status on failure', async () => {
        mockSpawnSync({ status: 2, stdout: '', stderr: 'not found' });
        const spies = spyLogger();
        const runner = new antigravity_1.AntigravityRunner();
        await runner.run('test prompt');
        const warnCalls = spies.warn.mock.calls.map((c) => c.arguments[0]);
        strict_1.default.ok(warnCalls.some((msg) => msg?.includes('status 2')));
    });
    (0, node_test_1.it)('returns empty output on failure', async () => {
        mockSpawnSync({ status: 1, stdout: '', stderr: 'err' });
        spyLogger();
        const runner = new antigravity_1.AntigravityRunner();
        const result = await runner.run('test prompt');
        strict_1.default.equal(result.output, '');
    });
});
(0, node_test_1.describe)('CodexRunner – failed tasks', () => {
    (0, node_test_1.beforeEach)(() => node_test_2.mock.restoreAll());
    (0, node_test_1.afterEach)(() => node_test_2.mock.restoreAll());
    (0, node_test_1.it)('returns success=false when codex exits with non-zero status', async () => {
        mockSpawnSync({ status: 1, stdout: '', stderr: 'codex failed' });
        spyLogger();
        const runner = new codex_1.CodexRunner();
        const result = await runner.run('test prompt');
        strict_1.default.equal(result.success, false);
        strict_1.default.equal(result.error, 'codex failed');
    });
    (0, node_test_1.it)('logs a warning with exit status on failure', async () => {
        mockSpawnSync({ status: 2, stdout: '', stderr: 'auth error' });
        const spies = spyLogger();
        const runner = new codex_1.CodexRunner();
        await runner.run('test prompt');
        const warnCalls = spies.warn.mock.calls.map((c) => c.arguments[0]);
        strict_1.default.ok(warnCalls.some((msg) => msg?.includes('status 2')));
    });
    (0, node_test_1.it)('returns empty output on failure', async () => {
        mockSpawnSync({ status: 1, stdout: '', stderr: 'err' });
        spyLogger();
        const runner = new codex_1.CodexRunner();
        const result = await runner.run('test prompt');
        strict_1.default.equal(result.output, '');
    });
});
// ─── CursorRunner ─────────────────────────────────────────────────────────────
(0, node_test_1.describe)('CursorRunner', () => {
    (0, node_test_1.it)('isAvailable returns boolean (depends on agent CLI in PATH)', () => {
        const runner = new cursor_1.CursorRunner();
        strict_1.default.equal(typeof runner.isAvailable(), 'boolean');
    });
});
(0, node_test_1.describe)('CursorRunner – failed tasks', () => {
    (0, node_test_1.beforeEach)(() => node_test_2.mock.restoreAll());
    (0, node_test_1.afterEach)(() => node_test_2.mock.restoreAll());
    (0, node_test_1.it)('returns success=false when cursor exits with non-zero status', async () => {
        mockSpawnSync({ status: 1, stdout: '', stderr: 'cursor failed' });
        spyLogger();
        const runner = new cursor_1.CursorRunner();
        const result = await runner.run('test prompt');
        strict_1.default.equal(result.success, false);
        strict_1.default.equal(result.error, 'cursor failed');
    });
    (0, node_test_1.it)('logs a warning with exit status on failure', async () => {
        mockSpawnSync({ status: 3, stdout: '', stderr: 'timeout' });
        const spies = spyLogger();
        const runner = new cursor_1.CursorRunner();
        await runner.run('test prompt');
        const warnCalls = spies.warn.mock.calls.map((c) => c.arguments[0]);
        strict_1.default.ok(warnCalls.some((msg) => msg?.includes('status 3')));
    });
    (0, node_test_1.it)('logs stderr on failure', async () => {
        mockSpawnSync({ status: 1, stdout: '', stderr: 'workspace not found' });
        const spies = spyLogger();
        const runner = new cursor_1.CursorRunner();
        await runner.run('test prompt');
        const warnCalls = spies.warn.mock.calls.map((c) => c.arguments[0]);
        strict_1.default.ok(warnCalls.some((msg) => msg?.includes('workspace not found')));
    });
    (0, node_test_1.it)('logs spawn error when present', async () => {
        mockSpawnSync({ status: null, stdout: '', stderr: '', error: new Error('EACCES') });
        const spies = spyLogger();
        const runner = new cursor_1.CursorRunner();
        await runner.run('test prompt');
        const warnCalls = spies.warn.mock.calls.map((c) => c.arguments[0]);
        strict_1.default.ok(warnCalls.some((msg) => msg?.includes('EACCES')));
    });
    (0, node_test_1.it)('returns empty output on failure', async () => {
        mockSpawnSync({ status: 1, stdout: '', stderr: 'err' });
        spyLogger();
        const runner = new cursor_1.CursorRunner();
        const result = await runner.run('test prompt');
        strict_1.default.equal(result.output, '');
    });
});
// ─── WindsurfRunner ───────────────────────────────────────────────────────────
(0, node_test_1.describe)('WindsurfRunner – failed tasks', () => {
    (0, node_test_1.beforeEach)(() => node_test_2.mock.restoreAll());
    (0, node_test_1.afterEach)(() => node_test_2.mock.restoreAll());
    (0, node_test_1.it)('returns success=false when windsurf exits with non-zero status', async () => {
        mockSpawnSync({ status: 1, stdout: '', stderr: 'windsurf crashed' });
        spyLogger();
        const runner = new windsurf_1.WindsurfRunner();
        const result = await runner.run('test prompt');
        strict_1.default.equal(result.success, false);
        strict_1.default.equal(result.error, 'windsurf crashed');
    });
    (0, node_test_1.it)('logs a warning with exit status on failure', async () => {
        mockSpawnSync({ status: 127, stdout: '', stderr: 'not found' });
        const spies = spyLogger();
        const runner = new windsurf_1.WindsurfRunner();
        await runner.run('test prompt');
        const warnCalls = spies.warn.mock.calls.map((c) => c.arguments[0]);
        strict_1.default.ok(warnCalls.some((msg) => msg?.includes('status 127')));
    });
    (0, node_test_1.it)('logs stderr on failure', async () => {
        mockSpawnSync({ status: 1, stdout: '', stderr: 'license expired' });
        const spies = spyLogger();
        const runner = new windsurf_1.WindsurfRunner();
        await runner.run('test prompt');
        const warnCalls = spies.warn.mock.calls.map((c) => c.arguments[0]);
        strict_1.default.ok(warnCalls.some((msg) => msg?.includes('license expired')));
    });
    (0, node_test_1.it)('logs spawn error when present', async () => {
        mockSpawnSync({ status: null, stdout: '', stderr: '', error: new Error('SIGTERM') });
        const spies = spyLogger();
        const runner = new windsurf_1.WindsurfRunner();
        await runner.run('test prompt');
        const warnCalls = spies.warn.mock.calls.map((c) => c.arguments[0]);
        strict_1.default.ok(warnCalls.some((msg) => msg?.includes('SIGTERM')));
    });
    (0, node_test_1.it)('returns empty output on failure', async () => {
        mockSpawnSync({ status: 1, stdout: '', stderr: 'err' });
        spyLogger();
        const runner = new windsurf_1.WindsurfRunner();
        const result = await runner.run('test prompt');
        strict_1.default.equal(result.output, '');
    });
});
(0, node_test_1.describe)('WindsurfRunner – process cleanup (non-Windows CLI mode)', { skip: platform_1.isWindows }, () => {
    (0, node_test_1.beforeEach)(() => node_test_2.mock.restoreAll());
    (0, node_test_1.afterEach)(() => node_test_2.mock.restoreAll());
    (0, node_test_1.it)('kills Windsurf IDE after run when it was not already running', async () => {
        const spawnMock = node_test_2.mock.method(childProcess, 'spawnSync', (command) => {
            const base = { pid: 1, output: [], stderr: '', status: 0, signal: null, error: undefined };
            if (command === 'pgrep')
                return { ...base, stdout: '', status: 1 };
            return { ...base, stdout: '' };
        });
        spyLogger();
        const runner = new windsurf_1.WindsurfRunner();
        await runner.run('test prompt');
        const calls = spawnMock.mock.calls.map((c) => c.arguments[0]);
        strict_1.default.ok(calls.some((cmd) => cmd === 'pkill'), `Expected pkill in spawn calls: [${calls}]`);
    });
    (0, node_test_1.it)('does not kill Windsurf IDE when it was already running', async () => {
        const spawnMock = node_test_2.mock.method(childProcess, 'spawnSync', (command) => {
            const base = { pid: 1, output: [], stderr: '', status: 0, signal: null, error: undefined };
            if (command === 'pgrep') {
                return { ...base, stdout: '1234' };
            }
            return { ...base, stdout: '' };
        });
        spyLogger();
        const runner = new windsurf_1.WindsurfRunner();
        await runner.run('test prompt');
        const calls = spawnMock.mock.calls.map((c) => c.arguments[0]);
        strict_1.default.ok(!calls.some((cmd) => cmd === 'pkill'), `Expected no pkill in spawn calls: [${calls}]`);
    });
});
(0, node_test_1.describe)('WindsurfRunner – Docker mode (Windows)', { skip: !platform_1.isWindows }, () => {
    (0, node_test_1.it)('isDockerWindsurfAvailable checks for docker and WINDSURF_TOKEN', () => {
        // This test reflects the real environment — just verify it returns a boolean
        strict_1.default.equal(typeof (0, windsurf_2.isDockerWindsurfAvailable)(), 'boolean');
    });
    (0, node_test_1.it)('isAvailable returns boolean on Windows', () => {
        const runner = new windsurf_1.WindsurfRunner();
        strict_1.default.equal(typeof runner.isAvailable(), 'boolean');
    });
});
function fakeSdk(messages) {
    const calls = [];
    const sdk = {
        query(args) {
            calls.push({
                prompt: args.prompt,
                options: args.options,
                envSnapshot: {
                    apiKey: process.env.ANTHROPIC_API_KEY,
                    baseUrl: process.env.ANTHROPIC_BASE_URL,
                },
            });
            return (async function* () {
                for (const m of messages)
                    yield m;
            })();
        },
    };
    return { sdk, calls };
}
function withAnthropicEnv(env, fn) {
    const prev = {
        apiKey: process.env.ANTHROPIC_API_KEY,
        baseUrl: process.env.ANTHROPIC_BASE_URL,
        model: process.env.ANTHROPIC_MODEL,
        maxRetries: process.env.ANTHROPIC_SDK_MAX_RETRIES,
    };
    if (env.apiKey !== undefined)
        process.env.ANTHROPIC_API_KEY = env.apiKey;
    else
        delete process.env.ANTHROPIC_API_KEY;
    if (env.baseUrl !== undefined)
        process.env.ANTHROPIC_BASE_URL = env.baseUrl;
    else
        delete process.env.ANTHROPIC_BASE_URL;
    if (env.model !== undefined)
        process.env.ANTHROPIC_MODEL = env.model;
    else
        delete process.env.ANTHROPIC_MODEL;
    if (env.maxRetries !== undefined)
        process.env.ANTHROPIC_SDK_MAX_RETRIES = env.maxRetries;
    else
        delete process.env.ANTHROPIC_SDK_MAX_RETRIES;
    return fn().finally(() => {
        if (prev.apiKey === undefined)
            delete process.env.ANTHROPIC_API_KEY;
        else
            process.env.ANTHROPIC_API_KEY = prev.apiKey;
        if (prev.baseUrl === undefined)
            delete process.env.ANTHROPIC_BASE_URL;
        else
            process.env.ANTHROPIC_BASE_URL = prev.baseUrl;
        if (prev.model === undefined)
            delete process.env.ANTHROPIC_MODEL;
        else
            process.env.ANTHROPIC_MODEL = prev.model;
        if (prev.maxRetries === undefined)
            delete process.env.ANTHROPIC_SDK_MAX_RETRIES;
        else
            process.env.ANTHROPIC_SDK_MAX_RETRIES = prev.maxRetries;
    });
}
(0, node_test_1.describe)('AnthropicSdkRunner', () => {
    (0, node_test_1.beforeEach)(() => node_test_2.mock.restoreAll());
    (0, node_test_1.afterEach)(() => node_test_2.mock.restoreAll());
    (0, node_test_1.it)('returns failure when no ANTHROPIC_API_KEY is configured', async () => {
        spyLogger();
        await withAnthropicEnv({}, async () => {
            const runner = new anthropicSdk_1.AnthropicSdkRunner();
            const result = await runner.run('do something');
            strict_1.default.equal(result.success, false);
            strict_1.default.equal(result.output, '');
            strict_1.default.match(result.error, /No ANTHROPIC_API_KEY/);
        });
    });
    (0, node_test_1.it)('concatenates assistant text and result into output', async () => {
        spyLogger();
        const { sdk } = fakeSdk([
            { type: 'assistant', message: { content: [{ type: 'text', text: 'hello ' }] } },
            { type: 'assistant', message: { content: [{ type: 'text', text: 'world' }] } },
            { type: 'result', result: 'final-summary' },
        ]);
        await withAnthropicEnv({ apiKey: 'sk-test' }, async () => {
            const runner = new anthropicSdk_1.AnthropicSdkRunner();
            node_test_2.mock.method(runner, 'loadSdk', async () => sdk);
            const result = await runner.run('please help');
            strict_1.default.equal(result.success, true);
            strict_1.default.equal(result.error, '');
            strict_1.default.equal(result.output, 'hello world\nfinal-summary');
        });
    });
    (0, node_test_1.it)('forwards cwd, claude_code preset, and default model to the SDK', async () => {
        spyLogger();
        const { sdk, calls } = fakeSdk([{ type: 'result', result: 'ok' }]);
        await withAnthropicEnv({ apiKey: 'sk-test' }, async () => {
            const runner = new anthropicSdk_1.AnthropicSdkRunner();
            node_test_2.mock.method(runner, 'loadSdk', async () => sdk);
            await runner.run('the prompt');
        });
        strict_1.default.equal(calls.length, 1);
        strict_1.default.equal(calls[0].prompt, 'the prompt');
        strict_1.default.equal(calls[0].options.cwd, process.cwd());
        strict_1.default.equal(calls[0].options.systemPrompt.type, 'preset');
        strict_1.default.equal(calls[0].options.systemPrompt.preset, 'claude_code');
        strict_1.default.equal(calls[0].options.permissionMode, 'acceptEdits');
        strict_1.default.equal(calls[0].options.model, 'claude-opus-4-6');
        strict_1.default.deepEqual(calls[0].options.allowedTools, ['Read', 'Write', 'Edit', 'Glob', 'Grep', 'Bash']);
    });
    (0, node_test_1.it)('uses ANTHROPIC_MODEL from env when set', async () => {
        spyLogger();
        const { sdk, calls } = fakeSdk([{ type: 'result', result: 'ok' }]);
        await withAnthropicEnv({ apiKey: 'sk-test', model: 'claude-sonnet-4-6' }, async () => {
            const runner = new anthropicSdk_1.AnthropicSdkRunner();
            node_test_2.mock.method(runner, 'loadSdk', async () => sdk);
            await runner.run('p');
        });
        strict_1.default.equal(calls[0].options.model, 'claude-sonnet-4-6');
    });
    (0, node_test_1.it)('sets ANTHROPIC_API_KEY during the call and restores it after', async () => {
        spyLogger();
        const { sdk, calls } = fakeSdk([{ type: 'result', result: 'ok' }]);
        await withAnthropicEnv({ apiKey: 'sk-original' }, async () => {
            const runner = new anthropicSdk_1.AnthropicSdkRunner();
            node_test_2.mock.method(runner, 'loadSdk', async () => sdk);
            await runner.run('p');
            // While the SDK was being called, the chosen token (the only one) was in env
            strict_1.default.equal(calls[0].envSnapshot.apiKey, 'sk-original');
            // After the call, env is restored to the original value
            strict_1.default.equal(process.env.ANTHROPIC_API_KEY, 'sk-original');
        });
    });
    (0, node_test_1.it)('sets ANTHROPIC_BASE_URL during the call when configured and restores after', async () => {
        spyLogger();
        const { sdk, calls } = fakeSdk([{ type: 'result', result: 'ok' }]);
        await withAnthropicEnv({ apiKey: 'sk-x', baseUrl: 'https://proxy.example.com' }, async () => {
            const runner = new anthropicSdk_1.AnthropicSdkRunner();
            node_test_2.mock.method(runner, 'loadSdk', async () => sdk);
            await runner.run('p');
            strict_1.default.equal(calls[0].envSnapshot.baseUrl, 'https://proxy.example.com');
            strict_1.default.equal(process.env.ANTHROPIC_BASE_URL, 'https://proxy.example.com');
        });
    });
    (0, node_test_1.it)('does not set ANTHROPIC_BASE_URL when not configured (and leaves it absent afterwards)', async () => {
        spyLogger();
        const { sdk, calls } = fakeSdk([{ type: 'result', result: 'ok' }]);
        await withAnthropicEnv({ apiKey: 'sk-x' }, async () => {
            const runner = new anthropicSdk_1.AnthropicSdkRunner();
            node_test_2.mock.method(runner, 'loadSdk', async () => sdk);
            await runner.run('p');
            strict_1.default.equal(calls[0].envSnapshot.baseUrl, undefined);
            strict_1.default.equal(process.env.ANTHROPIC_BASE_URL, undefined);
        });
    });
    (0, node_test_1.it)('rotates round-robin through a comma-separated token pool', async () => {
        spyLogger();
        const { sdk, calls } = fakeSdk([{ type: 'result', result: 'ok' }]);
        await withAnthropicEnv({ apiKey: 'k1,k2,k3' }, async () => {
            const runner = new anthropicSdk_1.AnthropicSdkRunner();
            node_test_2.mock.method(runner, 'loadSdk', async () => sdk);
            await runner.run('a');
            await runner.run('b');
            await runner.run('c');
            await runner.run('d');
        });
        strict_1.default.deepEqual(calls.map((c) => c.envSnapshot.apiKey), ['k1', 'k2', 'k3', 'k1']);
    });
    (0, node_test_1.it)('appends notes to the prompt as "Additional context"', async () => {
        spyLogger();
        const { sdk, calls } = fakeSdk([{ type: 'result', result: 'ok' }]);
        await withAnthropicEnv({ apiKey: 'sk-x' }, async () => {
            const runner = new anthropicSdk_1.AnthropicSdkRunner();
            node_test_2.mock.method(runner, 'loadSdk', async () => sdk);
            await runner.run('do X', 'be careful about Y');
        });
        strict_1.default.equal(calls[0].prompt, 'do X\n\nAdditional context:\nbe careful about Y');
    });
    (0, node_test_1.it)('returns success=false and logs a warning when the SDK throws', async () => {
        const spies = spyLogger();
        await withAnthropicEnv({ apiKey: 'sk-x', maxRetries: '0' }, async () => {
            const runner = new anthropicSdk_1.AnthropicSdkRunner();
            node_test_2.mock.method(runner, 'loadSdk', async () => {
                throw new Error('auth failed');
            });
            const result = await runner.run('p');
            strict_1.default.equal(result.success, false);
            strict_1.default.equal(result.error, 'auth failed');
        });
        const warnCalls = spies.warn.mock.calls.map((c) => c.arguments[0]);
        strict_1.default.ok(warnCalls.some((m) => m?.includes('auth failed')));
    });
    (0, node_test_1.it)('isAvailable returns false when no token is set even if the SDK is installed', async () => {
        spyLogger();
        await withAnthropicEnv({}, async () => {
            const runner = new anthropicSdk_1.AnthropicSdkRunner();
            strict_1.default.equal(runner.isAvailable(), false);
        });
    });
});
// ─── AnthropicSdkRunner – retry behavior ─────────────────────────────────────
(0, node_test_1.describe)('AnthropicSdkRunner – retries', () => {
    (0, node_test_1.beforeEach)(() => node_test_2.mock.restoreAll());
    (0, node_test_1.afterEach)(() => node_test_2.mock.restoreAll());
    (0, node_test_1.it)('retries transient SDK errors and eventually succeeds', async () => {
        spyLogger();
        const { sdk } = fakeSdk([
            { type: 'assistant', message: { content: [{ type: 'text', text: 'recovered' }] } },
            { type: 'result', result: 'ok' },
        ]);
        await withAnthropicEnv({ apiKey: 'sk-x', maxRetries: '3' }, async () => {
            const runner = new anthropicSdk_1.AnthropicSdkRunner();
            node_test_2.mock.method(runner, 'retryDelayMs', () => 0);
            // Fail the first two loadSdk calls; succeed on the third.
            let calls = 0;
            node_test_2.mock.method(runner, 'loadSdk', async () => {
                calls += 1;
                if (calls < 3)
                    throw new Error('ECONNRESET');
                return sdk;
            });
            const result = await runner.run('p');
            strict_1.default.equal(result.success, true);
            strict_1.default.equal(result.output, 'recovered\nok');
            strict_1.default.equal(result.error, '');
            strict_1.default.equal(calls, 3);
        });
    });
    (0, node_test_1.it)('honors ANTHROPIC_SDK_MAX_RETRIES and surfaces the last error after exhausting attempts', async () => {
        spyLogger();
        await withAnthropicEnv({ apiKey: 'sk-x', maxRetries: '2' }, async () => {
            const runner = new anthropicSdk_1.AnthropicSdkRunner();
            node_test_2.mock.method(runner, 'retryDelayMs', () => 0);
            let calls = 0;
            node_test_2.mock.method(runner, 'loadSdk', async () => {
                calls += 1;
                throw new Error(`fail ${calls}`);
            });
            const result = await runner.run('p');
            strict_1.default.equal(result.success, false);
            // 1 initial + 2 retries = 3 attempts
            strict_1.default.equal(calls, 3);
            strict_1.default.equal(result.error, 'fail 3');
        });
    });
    (0, node_test_1.it)('does not retry when ANTHROPIC_SDK_MAX_RETRIES=0', async () => {
        spyLogger();
        await withAnthropicEnv({ apiKey: 'sk-x', maxRetries: '0' }, async () => {
            const runner = new anthropicSdk_1.AnthropicSdkRunner();
            node_test_2.mock.method(runner, 'retryDelayMs', () => 0);
            let calls = 0;
            node_test_2.mock.method(runner, 'loadSdk', async () => {
                calls += 1;
                throw new Error('boom');
            });
            const result = await runner.run('p');
            strict_1.default.equal(result.success, false);
            strict_1.default.equal(calls, 1);
            strict_1.default.equal(result.error, 'boom');
        });
    });
    (0, node_test_1.it)('rotates to the next token on each retry attempt', async () => {
        spyLogger();
        await withAnthropicEnv({ apiKey: 'k1,k2,k3', maxRetries: '2' }, async () => {
            const runner = new anthropicSdk_1.AnthropicSdkRunner();
            node_test_2.mock.method(runner, 'retryDelayMs', () => 0);
            const seenKeys = [];
            node_test_2.mock.method(runner, 'loadSdk', async () => {
                seenKeys.push(process.env.ANTHROPIC_API_KEY);
                throw new Error('nope');
            });
            const result = await runner.run('p');
            strict_1.default.equal(result.success, false);
            strict_1.default.deepEqual(seenKeys, ['k1', 'k2', 'k3']);
        });
    });
});
(0, node_test_1.describe)('getAnthropicSdkMaxRetries', () => {
    const prev = process.env.ANTHROPIC_SDK_MAX_RETRIES;
    (0, node_test_1.afterEach)(() => {
        if (prev === undefined)
            delete process.env.ANTHROPIC_SDK_MAX_RETRIES;
        else
            process.env.ANTHROPIC_SDK_MAX_RETRIES = prev;
    });
    (0, node_test_1.it)('defaults to 3 when the env var is unset', () => {
        delete process.env.ANTHROPIC_SDK_MAX_RETRIES;
        strict_1.default.equal((0, anthropicSdk_1.getAnthropicSdkMaxRetries)(), 3);
    });
    (0, node_test_1.it)('returns the parsed env value when valid', () => {
        process.env.ANTHROPIC_SDK_MAX_RETRIES = '7';
        strict_1.default.equal((0, anthropicSdk_1.getAnthropicSdkMaxRetries)(), 7);
    });
    (0, node_test_1.it)('accepts 0 as a valid value (disables retries)', () => {
        process.env.ANTHROPIC_SDK_MAX_RETRIES = '0';
        strict_1.default.equal((0, anthropicSdk_1.getAnthropicSdkMaxRetries)(), 0);
    });
    (0, node_test_1.it)('falls back to the default on non-numeric input', () => {
        process.env.ANTHROPIC_SDK_MAX_RETRIES = 'foo';
        strict_1.default.equal((0, anthropicSdk_1.getAnthropicSdkMaxRetries)(), 3);
    });
    (0, node_test_1.it)('falls back to the default on negative numbers', () => {
        process.env.ANTHROPIC_SDK_MAX_RETRIES = '-1';
        strict_1.default.equal((0, anthropicSdk_1.getAnthropicSdkMaxRetries)(), 3);
    });
});
// ─── createRunners ────────────────────────────────────────────────────────────
function makeConfig(agents) {
    return { agents };
}
(0, node_test_1.describe)('createRunners', () => {
    (0, node_test_1.beforeEach)(() => node_test_2.mock.restoreAll());
    (0, node_test_1.afterEach)(() => node_test_2.mock.restoreAll());
    (0, node_test_1.it)('returns runners in the order specified by config.agents', () => {
        spyLogger();
        const runners = (0, index_1.createRunners)(makeConfig(['cursor', 'codex', 'windsurf', 'claude', 'antigravity']));
        strict_1.default.deepEqual(runners.map((r) => r.name), ['cursor', 'codex', 'windsurf', 'claude', 'antigravity']);
    });
    (0, node_test_1.it)('returns runners in reversed order when config specifies reversed order', () => {
        spyLogger();
        const runners = (0, index_1.createRunners)(makeConfig(['claude', 'cursor']));
        strict_1.default.deepEqual(runners.map((r) => r.name), ['claude', 'cursor']);
    });
    (0, node_test_1.it)('returns a single runner when only one agent is configured', () => {
        spyLogger();
        const runners = (0, index_1.createRunners)(makeConfig(['claude']));
        strict_1.default.equal(runners.length, 1);
        strict_1.default.equal(runners[0].name, 'claude');
    });
    (0, node_test_1.it)('logs configured runners at info level', () => {
        const spies = spyLogger();
        (0, index_1.createRunners)(makeConfig(['cursor', 'claude']));
        const infoCalls = spies.info.mock.calls.map((c) => c.arguments[0]);
        strict_1.default.ok(infoCalls.some((msg) => msg?.includes('cursor') && msg?.includes('claude') && msg?.includes('Configured runners')));
    });
    (0, node_test_1.it)('logs a warning when a configured runner is not available', () => {
        const spies = spyLogger();
        (0, index_1.createRunners)(makeConfig(['windsurf', 'claude']));
        const warnCalls = spies.warn.mock.calls.map((c) => c.arguments[0]);
        const hasUnavailableWarning = warnCalls.some((msg) => msg?.includes('not found'));
        // At least windsurf should be flagged (it's typically not installed in CI)
        // We can't assert strongly since CI might have different tool availability
        strict_1.default.ok(typeof hasUnavailableWarning === 'boolean');
    });
    (0, node_test_1.it)('includes unavailable runners in the returned array (filtering is done by callers)', () => {
        spyLogger();
        const runners = (0, index_1.createRunners)(makeConfig(['windsurf', 'claude']));
        strict_1.default.equal(runners.length, 2);
        strict_1.default.equal(runners[0].name, 'windsurf');
        strict_1.default.equal(runners[1].name, 'claude');
    });
});
//# sourceMappingURL=ai-runners.test.js.map