vtcode 0.172.2

A Rust-based terminal coding agent with modular architecture supporting multiple LLM providers
const { test, beforeEach } = require("node:test");
const assert = require("node:assert/strict");
const { EventEmitter } = require("node:events");
const childProcess = require("node:child_process");
const compileModules = require("./helpers/compileModules.cjs");
const { load, vscode } = compileModules([
    "services/processExecution", "utils/vtcodeRunner", "commands/configurationCommands",
], { vtcodeConfig: "vtcodeConfig.cjs" });
const { executeVtcodeProcess } = load("services/processExecution");
const { runVtcodeCommand } = load("utils/vtcodeRunner");
const { getConfigArguments } = load("utils/vtcodeRunner");
const { registerConfigurationCommands } = load("commands/configurationCommands");
const config = load("vtcodeConfig").state;

beforeEach(() => {
    vscode.workspace.isTrusted = true;
    vscode.env.uiKind = vscode.UIKind.Desktop;
    vscode.testState.handlers.clear();
    vscode.testState.messages.length = 0;
    vscode.testState.picks.length = 0;
    vscode.testState.beforeProgress = undefined;
    config.calls.length = 0;
    config.uri = undefined;
    config.summary = undefined;
    config.pick = undefined;
    config.updated = true;
    config.providerResult = "updated";
});
function output() {
    const text = [];
    return { text, append: (value) => text.push(value), appendLine() {}, show() {} };
}
function child() {
    const process = new EventEmitter();
    process.stdout = new EventEmitter();
    process.stderr = new EventEmitter();
    process.killed = false;
    process.kill = () => { process.killed = true; };
    return process;
}
function uri(path) { return { path, toString: () => path }; }
function configServices(getSummary, trusted = true) {
    const errors = [];
    const lines = [];
    const trustRequests = [];
    return {
        errors, lines, trustRequests,
        getCurrentConfigSummary: getSummary,
        ensureWorkspaceTrustedForCommand: async (action) => { trustRequests.push(action); return trusted; },
        getOutputChannel: () => ({ appendLine: (line) => lines.push(line) }),
        handleCommandError: (...args) => errors.push(args),
        openToolsPolicyGuide: async () => {}, openMcpGuide: async () => {},
    };
}

test("config argument adapter preserves absent configuration and literal paths", () => {
    assert.deepEqual(getConfigArguments(), []);
    assert.deepEqual(getConfigArguments({ fsPath: "/workspace/config;with spaces.toml" }),
        ["--config", "/workspace/config;with spaces.toml"]);
});

test("shared process preserves literal argv, stream callbacks and success", async (t) => {
    const process = child();
    const calls = [];
    t.mock.method(childProcess, "spawn", (...args) => { calls.push(args); return process; });
    const channel = output();
    const stdout = [];
    const stderr = [];
    const args = ["--config", "/workspace/a;$(printf injected).toml", "exec", "text with spaces & | >"];
    const spawnOptions = { cwd: "/workspace", env: { VT_IDE_CONTEXT_FILE: "/context.json" } };
    const pending = executeVtcodeProcess("/custom/vtcode", args, channel, () => spawnOptions, {
        showProgress: false, onStdout: (text) => stdout.push(text), onStderr: (text) => stderr.push(text),
    });
    assert.deepEqual(calls, [["/custom/vtcode", args, spawnOptions]]);
    assert.equal(calls[0][1], args);
    assert.equal(calls[0][2].shell, undefined);
    process.stdout.emit("data", Buffer.from("out"));
    process.stderr.emit("data", Buffer.from("warning"));
    process.emit("close", 0);
    await pending;
    assert.deepEqual(channel.text, ["out", "warning"]);
    assert.deepEqual(stdout, ["out"]);
    assert.deepEqual(stderr, ["warning"]);
});

test("real process receives shell metacharacters as arguments and the supplied environment", async () => {
    const args = ["two words", "$(printf injected)", "; printf another", "quote'and\"text", "unicode-Ä‘"];
    const channel = output();
    await executeVtcodeProcess(process.execPath, [
        "-e", "process.stdout.write(JSON.stringify({ args: process.argv.slice(1), marker: process.env.VTCODE_EXECUTION_TEST_VALUE }))",
        ...args,
    ], channel, () => ({ cwd: __dirname, env: { VTCODE_EXECUTION_TEST_VALUE: "only supplied marker" } }),
    { showProgress: false });
    assert.deepEqual(JSON.parse(channel.text.join("")), { args, marker: "only supplied marker" });
});

test("shared process keeps cancellation distinct from failure and disposes subscriptions", async (t) => {
    const process = child();
    t.mock.method(childProcess, "spawn", () => process);
    let cancel;
    let disposed = 0;
    const pending = executeVtcodeProcess("vtcode", [], output(), () => ({}), {
        showProgress: false,
        cancellationToken: {
            onCancellationRequested: (callback) => { cancel = callback; return { dispose: () => { disposed += 1; } }; },
        },
    });
    cancel();
    assert.equal(process.killed, true);
    process.emit("close", 0);
    await assert.rejects(pending, vscode.CancellationError);
    assert.equal(disposed, 1);

    const failed = child();
    t.mock.method(childProcess, "spawn", () => failed);
    const failedRun = executeVtcodeProcess("vtcode", [], output(), () => ({}), { showProgress: false });
    failed.emit("close", 9);
    await assert.rejects(failedRun, /VT Code exited with code 9/);
});

test("shared process propagates spawn errors and reads environment inside progress callback", async (t) => {
    const failure = new Error("spawn denied");
    t.mock.method(childProcess, "spawn", () => { throw failure; });
    await assert.rejects(executeVtcodeProcess("vtcode", [], output(), () => ({}), { showProgress: false }),
        (error) => error === failure);
    const process = child();
    let env = { snapshot: "before" };
    let captured;
    t.mock.method(childProcess, "spawn", (_path, _args, options) => { captured = options; return process; });
    vscode.testState.beforeProgress = () => { env = { snapshot: "after" }; };
    const pending = executeVtcodeProcess("vtcode", [], output(), () => ({ env }));
    assert.deepEqual(captured, { env: { snapshot: "after" } });
    process.emit("error", failure);
    await assert.rejects(pending, (error) => error === failure);
});

test("modular caller rejects untrusted, web and already-cancelled requests before spawning", async (t) => {
    const spawn = t.mock.method(childProcess, "spawn", () => { throw new Error("must not spawn"); });
    vscode.workspace.isTrusted = false;
    await assert.rejects(runVtcodeCommand(["analyze"]), /Trust this workspace/);
    vscode.workspace.isTrusted = true;
    vscode.env.uiKind = vscode.UIKind.Web;
    await assert.rejects(runVtcodeCommand(["analyze"]), /web extension host/);
    vscode.env.uiKind = vscode.UIKind.Desktop;
    await assert.rejects(runVtcodeCommand(["analyze"], { cancellationToken: { isCancellationRequested: true } }),
        vscode.CancellationError);
    assert.equal(spawn.mock.callCount(), 0);
});

test("configuration registrations preserve trust gates and disposable ownership", async () => {
    const services = configServices(() => undefined, false);
    const registrations = registerConfigurationCommands(services);
    assert.deepEqual([...vscode.testState.handlers.keys()], [
        "vtcode.toggleHumanInTheLoop", "vtcode.openToolsPolicyGuide", "vtcode.openToolsPolicyConfig", "vtcode.configureMcpProviders",
    ]);
    await vscode.testState.handlers.get("vtcode.toggleHumanInTheLoop")();
    await vscode.testState.handlers.get("vtcode.configureMcpProviders")();
    assert.equal(services.trustRequests.length, 2);
    assert.deepEqual(config.calls, []);
    assert.deepEqual(services.errors, []);
    for (const registration of registrations) registration.dispose();
    assert.equal(vscode.testState.handlers.size, 0);
});

test("HITL toggle reads the current selected configuration after awaiting the picker", async () => {
    const first = uri("/workspace/first.toml");
    const second = uri("/workspace/second.toml");
    let summary = { hasConfig: true, uri: first, humanInTheLoop: false, mcpProviders: [] };
    config.pick = async () => {
        summary = { ...summary, uri: second, humanInTheLoop: true };
        return second;
    };
    const services = configServices(() => summary);
    registerConfigurationCommands(services);
    await vscode.testState.handlers.get("vtcode.toggleHumanInTheLoop")();
    assert.deepEqual(config.calls, [["pick", first], ["hitl", second, false]]);
    assert.equal(services.lines.length, 1);
    assert.deepEqual(services.errors, []);
});

test("HITL toggle loads mismatched summaries and reports unsuccessful updates", async () => {
    const selected = uri("/workspace/other.toml");
    config.uri = selected;
    config.summary = { hasConfig: true, uri: selected, humanInTheLoop: false, mcpProviders: [] };
    config.updated = false;
    const services = configServices(() => ({ uri: uri("/workspace/stale.toml"), humanInTheLoop: true }));
    registerConfigurationCommands(services);
    await vscode.testState.handlers.get("vtcode.toggleHumanInTheLoop")();
    assert.deepEqual(config.calls.slice(1), [["load", selected], ["hitl", selected, true]]);
    assert.match(vscode.testState.messages[0], /Failed to update human_in_the_loop/);
    assert.deepEqual(services.lines, []);
});

test("configuration errors retain command context and original failure", async () => {
    const failure = new Error("Config picker failed");
    config.pick = async () => { throw failure; };
    const services = configServices(() => undefined);
    registerConfigurationCommands(services);
    await vscode.testState.handlers.get("vtcode.toggleHumanInTheLoop")();
    assert.deepEqual(services.errors, [["toggle human-in-the-loop mode", failure]]);
    assert.ok(config.calls.every((call) => call[0] === "pick"));
    assert.deepEqual(services.lines, []);
});

test("MCP toggle preserves provider-specific state and handles missing entries", async () => {
    const selected = uri("/workspace/vtcode.toml");
    config.uri = selected;
    const services = configServices(() => ({
        uri: selected, mcpProviders: [{ name: "on", enabled: true }, { name: "off", enabled: false }],
    }));
    registerConfigurationCommands(services);
    vscode.testState.picks.push({ action: "toggle", providerName: "off" });
    await vscode.testState.handlers.get("vtcode.configureMcpProviders")();
    assert.deepEqual(config.calls[1], ["mcp", selected, "off", true]);
    config.providerResult = "notfound";
    vscode.testState.picks.push({ action: "toggle", providerName: "on" });
    await vscode.testState.handlers.get("vtcode.configureMcpProviders")();
    assert.deepEqual(config.calls[3], ["mcp", selected, "on", false]);
    assert.match(vscode.testState.messages.at(-1), /was not found in vtcode.toml/);
    assert.equal(services.lines.length, 1);
});