CoolFace
Datasetpublic

basant307/AI_Governance_Project

sourceHugging Faceapache-2.0updated 2mo agoView on Hugging Face
0likes45downloads
FileCommandLoader.test.ts1284 linesDownload Raw Back to services
1/**2 * @license3 * Copyright 2025 Google LLC4 * SPDX-License-Identifier: Apache-2.05 */6 7import * as path from 'node:path';8import type { Config } from '@qwen-code/qwen-code-core';9import { Storage } from '@qwen-code/qwen-code-core';10import mock from 'mock-fs';11import { FileCommandLoader } from './FileCommandLoader.js';12import { assert, vi } from 'vitest';13import { createMockCommandContext } from '../test-utils/mockCommandContext.js';14import {15  SHELL_INJECTION_TRIGGER,16  SHORTHAND_ARGS_PLACEHOLDER,17  type PromptPipelineContent,18} from './prompt-processors/types.js';19import {20  ConfirmationRequiredError,21  ShellProcessor,22} from './prompt-processors/shellProcessor.js';23import { DefaultArgumentProcessor } from './prompt-processors/argumentProcessor.js';24import type { CommandContext } from '../ui/commands/types.js';25import { AtFileProcessor } from './prompt-processors/atFileProcessor.js';26 27const mockShellProcess = vi.hoisted(() => vi.fn());28const mockAtFileProcess = vi.hoisted(() => vi.fn());29vi.mock('./prompt-processors/atFileProcessor.js', () => ({30  AtFileProcessor: vi.fn().mockImplementation(() => ({31    process: mockAtFileProcess,32  })),33}));34vi.mock('./prompt-processors/shellProcessor.js', () => ({35  ShellProcessor: vi.fn().mockImplementation(() => ({36    process: mockShellProcess,37  })),38  ConfirmationRequiredError: class extends Error {39    constructor(40      message: string,41      public commandsToConfirm: string[],42    ) {43      super(message);44      this.name = 'ConfirmationRequiredError';45    }46  },47}));48 49vi.mock('./prompt-processors/argumentProcessor.js', async (importOriginal) => {50  const original =51    await importOriginal<52      typeof import('./prompt-processors/argumentProcessor.js')53    >();54  return {55    DefaultArgumentProcessor: vi56      .fn()57      .mockImplementation(() => new original.DefaultArgumentProcessor()),58  };59});60vi.mock('@qwen-code/qwen-code-core', async (importOriginal) => {61  const original =62    await importOriginal<typeof import('@qwen-code/qwen-code-core')>();63  return {64    ...original,65    Storage: original.Storage,66    isCommandAllowed: vi.fn(),67    ShellExecutionService: {68      execute: vi.fn(),69    },70  };71});72 73describe('FileCommandLoader', () => {74  const signal: AbortSignal = new AbortController().signal;75 76  beforeEach(() => {77    vi.clearAllMocks();78    mockShellProcess.mockImplementation(79      (prompt: PromptPipelineContent, context: CommandContext) => {80        const userArgsRaw = context?.invocation?.args || '';81        // This is a simplified mock. A real implementation would need to iterate82        // through all parts and process only the text parts.83        const firstTextPart = prompt.find(84          (p) => typeof p === 'string' || 'text' in p,85        );86        let textContent = '';87        if (typeof firstTextPart === 'string') {88          textContent = firstTextPart;89        } else if (firstTextPart && 'text' in firstTextPart) {90          textContent = firstTextPart.text ?? '';91        }92 93        const processedText = textContent.replaceAll(94          SHORTHAND_ARGS_PLACEHOLDER,95          userArgsRaw,96        );97        return Promise.resolve([{ text: processedText }]);98      },99    );100    mockAtFileProcess.mockImplementation(async (prompt: string) => prompt);101  });102 103  afterEach(() => {104    mock.restore();105  });106 107  it('loads a single command from a file', async () => {108    const userCommandsDir = Storage.getUserCommandsDir();109    mock({110      [userCommandsDir]: {111        'test.toml': 'prompt = "This is a test prompt"',112      },113    });114 115    const loader = new FileCommandLoader(null);116    const commands = await loader.loadCommands(signal);117 118    expect(commands).toHaveLength(1);119    const command = commands[0];120    expect(command).toBeDefined();121    expect(command.name).toBe('test');122    expect(command.sourceLabel).toBe('Custom');123    expect(command.sourceDetail).toBe('custom');124 125    const result = await command.action?.(126      createMockCommandContext({127        invocation: {128          raw: '/test',129          name: 'test',130          args: '',131        },132      }),133      '',134    );135    if (result?.type === 'submit_prompt') {136      expect(result.content).toEqual([{ text: 'This is a test prompt' }]);137    } else {138      assert.fail('Incorrect action type');139    }140  });141 142  // Symlink creation on Windows requires special permissions that are not143  // available in the standard CI environment. Therefore, we skip these tests144  // on Windows to prevent CI failures. The core functionality is still145  // validated on Linux and macOS.146  const itif = (condition: boolean) => (condition ? it : it.skip);147 148  itif(process.platform !== 'win32')(149    'loads commands from a symlinked directory',150    async () => {151      const userCommandsDir = Storage.getUserCommandsDir();152      const realCommandsDir = '/real/commands';153      mock({154        [realCommandsDir]: {155          'test.toml': 'prompt = "This is a test prompt"',156        },157        // Symlink the user commands directory to the real one158        [userCommandsDir]: mock.symlink({159          path: realCommandsDir,160        }),161      });162 163      const loader = new FileCommandLoader(null as unknown as Config);164      const commands = await loader.loadCommands(signal);165 166      expect(commands).toHaveLength(1);167      const command = commands[0];168      expect(command).toBeDefined();169      expect(command.name).toBe('test');170    },171  );172 173  itif(process.platform !== 'win32')(174    'loads commands from a symlinked subdirectory',175    async () => {176      const userCommandsDir = Storage.getUserCommandsDir();177      const realNamespacedDir = '/real/namespaced-commands';178      mock({179        [userCommandsDir]: {180          namespaced: mock.symlink({181            path: realNamespacedDir,182          }),183        },184        [realNamespacedDir]: {185          'my-test.toml': 'prompt = "This is a test prompt"',186        },187      });188 189      const loader = new FileCommandLoader(null as unknown as Config);190      const commands = await loader.loadCommands(signal);191 192      expect(commands).toHaveLength(1);193      const command = commands[0];194      expect(command).toBeDefined();195      expect(command.name).toBe('namespaced:my-test');196    },197  );198 199  it('loads multiple commands', async () => {200    const userCommandsDir = Storage.getUserCommandsDir();201    mock({202      [userCommandsDir]: {203        'test1.toml': 'prompt = "Prompt 1"',204        'test2.toml': 'prompt = "Prompt 2"',205      },206    });207 208    const loader = new FileCommandLoader(null);209    const commands = await loader.loadCommands(signal);210 211    expect(commands).toHaveLength(2);212  });213 214  it('creates deeply nested namespaces correctly', async () => {215    const userCommandsDir = Storage.getUserCommandsDir();216 217    mock({218      [userCommandsDir]: {219        gcp: {220          pipelines: {221            'run.toml': 'prompt = "run pipeline"',222          },223        },224      },225    });226    const mockConfig = {227      getProjectRoot: vi.fn(() => '/path/to/project'),228      getExtensions: vi.fn(() => []),229      getFolderTrustFeature: vi.fn(() => false),230      getFolderTrust: vi.fn(() => false),231    } as unknown as Config;232    const loader = new FileCommandLoader(mockConfig);233    const commands = await loader.loadCommands(signal);234    expect(commands).toHaveLength(1);235    expect(commands[0]!.name).toBe('gcp:pipelines:run');236  });237 238  it('creates namespaces from nested directories', async () => {239    const userCommandsDir = Storage.getUserCommandsDir();240    mock({241      [userCommandsDir]: {242        git: {243          'commit.toml': 'prompt = "git commit prompt"',244        },245      },246    });247 248    const loader = new FileCommandLoader(null);249    const commands = await loader.loadCommands(signal);250 251    expect(commands).toHaveLength(1);252    const command = commands[0];253    expect(command).toBeDefined();254    expect(command.name).toBe('git:commit');255  });256 257  it('returns both user and project commands in order', async () => {258    const userCommandsDir = Storage.getUserCommandsDir();259    const projectCommandsDir = new Storage(260      process.cwd(),261    ).getProjectCommandsDir();262    mock({263      [userCommandsDir]: {264        'test.toml': 'prompt = "User prompt"',265      },266      [projectCommandsDir]: {267        'test.toml': 'prompt = "Project prompt"',268      },269    });270 271    const mockConfig = {272      getProjectRoot: vi.fn(() => process.cwd()),273      getExtensions: vi.fn(() => []),274      getFolderTrustFeature: vi.fn(() => false),275      getFolderTrust: vi.fn(() => false),276    } as unknown as Config;277    const loader = new FileCommandLoader(mockConfig);278    const commands = await loader.loadCommands(signal);279 280    expect(commands).toHaveLength(2);281    const userResult = await commands[0].action?.(282      createMockCommandContext({283        invocation: {284          raw: '/test',285          name: 'test',286          args: '',287        },288      }),289      '',290    );291    if (userResult?.type === 'submit_prompt') {292      expect(userResult.content).toEqual([{ text: 'User prompt' }]);293    } else {294      assert.fail('Incorrect action type for user command');295    }296    const projectResult = await commands[1].action?.(297      createMockCommandContext({298        invocation: {299          raw: '/test',300          name: 'test',301          args: '',302        },303      }),304      '',305    );306    if (projectResult?.type === 'submit_prompt') {307      expect(projectResult.content).toEqual([{ text: 'Project prompt' }]);308    } else {309      assert.fail('Incorrect action type for project command');310    }311  });312 313  it('skips auto-discovered commands in bare mode', async () => {314    const userCommandsDir = Storage.getUserCommandsDir();315    const projectCommandsDir = new Storage(316      process.cwd(),317    ).getProjectCommandsDir();318    mock({319      [userCommandsDir]: {320        'user.toml': 'prompt = "User prompt"',321      },322      [projectCommandsDir]: {323        'project.toml': 'prompt = "Project prompt"',324      },325    });326 327    const mockConfig = {328      getProjectRoot: vi.fn(() => process.cwd()),329      getExtensions: vi.fn(() => []),330      getFolderTrustFeature: vi.fn(() => false),331      getFolderTrust: vi.fn(() => false),332      getBareMode: vi.fn(() => true),333    } as unknown as Config;334    const loader = new FileCommandLoader(mockConfig);335    const commands = await loader.loadCommands(signal);336 337    expect(commands).toEqual([]);338  });339 340  it('ignores files with TOML syntax errors', async () => {341    const userCommandsDir = Storage.getUserCommandsDir();342    mock({343      [userCommandsDir]: {344        'invalid.toml': 'this is not valid toml',345        'good.toml': 'prompt = "This one is fine"',346      },347    });348 349    const loader = new FileCommandLoader(null);350    const commands = await loader.loadCommands(signal);351 352    expect(commands).toHaveLength(1);353    expect(commands[0].name).toBe('good');354  });355 356  it('ignores files that are semantically invalid (missing prompt)', async () => {357    const userCommandsDir = Storage.getUserCommandsDir();358    mock({359      [userCommandsDir]: {360        'no_prompt.toml': 'description = "This file is missing a prompt"',361        'good.toml': 'prompt = "This one is fine"',362      },363    });364 365    const loader = new FileCommandLoader(null);366    const commands = await loader.loadCommands(signal);367 368    expect(commands).toHaveLength(1);369    expect(commands[0].name).toBe('good');370  });371 372  it('handles filename edge cases correctly', async () => {373    const userCommandsDir = Storage.getUserCommandsDir();374    mock({375      [userCommandsDir]: {376        'test.v1.toml': 'prompt = "Test prompt"',377      },378    });379 380    const loader = new FileCommandLoader(null);381    const commands = await loader.loadCommands(signal);382    const command = commands[0];383    expect(command).toBeDefined();384    expect(command.name).toBe('test.v1');385  });386 387  it('handles file system errors gracefully', async () => {388    mock({}); // Mock an empty file system389    const loader = new FileCommandLoader(null);390    const commands = await loader.loadCommands(signal);391    expect(commands).toHaveLength(0);392  });393 394  it('uses a default description if not provided', async () => {395    const userCommandsDir = Storage.getUserCommandsDir();396    mock({397      [userCommandsDir]: {398        'test.toml': 'prompt = "Test prompt"',399      },400    });401 402    const loader = new FileCommandLoader(null);403    const commands = await loader.loadCommands(signal);404    const command = commands[0];405    expect(command).toBeDefined();406    expect(command.description).toBe('Custom command from test.toml');407  });408 409  it('uses the provided description', async () => {410    const userCommandsDir = Storage.getUserCommandsDir();411    mock({412      [userCommandsDir]: {413        'test.toml': 'prompt = "Test prompt"\ndescription = "My test command"',414      },415    });416 417    const loader = new FileCommandLoader(null);418    const commands = await loader.loadCommands(signal);419    const command = commands[0];420    expect(command).toBeDefined();421    expect(command.description).toBe('My test command');422  });423 424  it('should sanitize colons in filenames to prevent namespace conflicts', async () => {425    const userCommandsDir = Storage.getUserCommandsDir();426    mock({427      [userCommandsDir]: {428        'legacy:command.toml': 'prompt = "This is a legacy command"',429      },430    });431 432    const loader = new FileCommandLoader(null);433    const commands = await loader.loadCommands(signal);434 435    expect(commands).toHaveLength(1);436    const command = commands[0];437    expect(command).toBeDefined();438 439    // Verify that the ':' in the filename was replaced with an '_'440    expect(command.name).toBe('legacy_command');441  });442 443  describe('Processor Instantiation Logic', () => {444    it('instantiates only DefaultArgumentProcessor if no {{args}} or !{} are present', async () => {445      const userCommandsDir = Storage.getUserCommandsDir();446      mock({447        [userCommandsDir]: {448          'simple.toml': `prompt = "Just a regular prompt"`,449        },450      });451 452      const loader = new FileCommandLoader(null as unknown as Config);453      await loader.loadCommands(signal);454 455      expect(ShellProcessor).not.toHaveBeenCalled();456      expect(DefaultArgumentProcessor).toHaveBeenCalledTimes(1);457    });458 459    it('instantiates only ShellProcessor if {{args}} is present (but not !{})', async () => {460      const userCommandsDir = Storage.getUserCommandsDir();461      mock({462        [userCommandsDir]: {463          'args.toml': `prompt = "Prompt with {{args}}"`,464        },465      });466 467      const loader = new FileCommandLoader(null as unknown as Config);468      await loader.loadCommands(signal);469 470      expect(ShellProcessor).toHaveBeenCalledTimes(1);471      expect(DefaultArgumentProcessor).not.toHaveBeenCalled();472    });473 474    it('instantiates ShellProcessor and DefaultArgumentProcessor if !{} is present (but not {{args}})', async () => {475      const userCommandsDir = Storage.getUserCommandsDir();476      mock({477        [userCommandsDir]: {478          'shell.toml': `prompt = "Prompt with !{cmd}"`,479        },480      });481 482      const loader = new FileCommandLoader(null as unknown as Config);483      await loader.loadCommands(signal);484 485      expect(ShellProcessor).toHaveBeenCalledTimes(1);486      expect(DefaultArgumentProcessor).toHaveBeenCalledTimes(1);487    });488 489    it('instantiates only ShellProcessor if both {{args}} and !{} are present', async () => {490      const userCommandsDir = Storage.getUserCommandsDir();491      mock({492        [userCommandsDir]: {493          'both.toml': `prompt = "Prompt with {{args}} and !{cmd}"`,494        },495      });496 497      const loader = new FileCommandLoader(null as unknown as Config);498      await loader.loadCommands(signal);499 500      expect(ShellProcessor).toHaveBeenCalledTimes(1);501      expect(DefaultArgumentProcessor).not.toHaveBeenCalled();502    });503 504    it('instantiates AtFileProcessor and DefaultArgumentProcessor if @{} is present', async () => {505      const userCommandsDir = Storage.getUserCommandsDir();506      mock({507        [userCommandsDir]: {508          'at-file.toml': `prompt = "Context: @{./my-file.txt}"`,509        },510      });511 512      const loader = new FileCommandLoader(null as unknown as Config);513      await loader.loadCommands(signal);514 515      expect(AtFileProcessor).toHaveBeenCalledTimes(1);516      expect(ShellProcessor).not.toHaveBeenCalled();517      expect(DefaultArgumentProcessor).toHaveBeenCalledTimes(1);518    });519 520    it('instantiates ShellProcessor and AtFileProcessor if !{} and @{} are present', async () => {521      const userCommandsDir = Storage.getUserCommandsDir();522      mock({523        [userCommandsDir]: {524          'shell-and-at.toml': `prompt = "Run !{cmd} with @{file.txt}"`,525        },526      });527 528      const loader = new FileCommandLoader(null as unknown as Config);529      await loader.loadCommands(signal);530 531      expect(ShellProcessor).toHaveBeenCalledTimes(1);532      expect(AtFileProcessor).toHaveBeenCalledTimes(1);533      expect(DefaultArgumentProcessor).toHaveBeenCalledTimes(1); // because no {{args}}534    });535 536    it('instantiates only ShellProcessor and AtFileProcessor if {{args}} and @{} are present', async () => {537      const userCommandsDir = Storage.getUserCommandsDir();538      mock({539        [userCommandsDir]: {540          'args-and-at.toml': `prompt = "Run {{args}} with @{file.txt}"`,541        },542      });543 544      const loader = new FileCommandLoader(null as unknown as Config);545      await loader.loadCommands(signal);546 547      expect(ShellProcessor).toHaveBeenCalledTimes(1);548      expect(AtFileProcessor).toHaveBeenCalledTimes(1);549      expect(DefaultArgumentProcessor).not.toHaveBeenCalled();550    });551  });552 553  describe('Extension Command Loading', () => {554    it('loads commands from active extensions', async () => {555      const userCommandsDir = Storage.getUserCommandsDir();556      const projectCommandsDir = new Storage(557        process.cwd(),558      ).getProjectCommandsDir();559      const extensionDir = path.join(560        process.cwd(),561        '.qwen/extensions/test-ext',562      );563 564      mock({565        [userCommandsDir]: {566          'user.toml': 'prompt = "User command"',567        },568        [projectCommandsDir]: {569          'project.toml': 'prompt = "Project command"',570        },571        [extensionDir]: {572          'qwen-extension.json': JSON.stringify({573            name: 'test-ext',574            version: '1.0.0',575          }),576          commands: {577            'ext.toml': 'prompt = "Extension command"',578          },579        },580      });581 582      const mockConfig = {583        getProjectRoot: vi.fn(() => process.cwd()),584        getExtensions: vi.fn(() => [585          {586            name: 'test-ext',587            version: '1.0.0',588            isActive: true,589            path: extensionDir,590          },591        ]),592        getFolderTrustFeature: vi.fn(() => false),593        getFolderTrust: vi.fn(() => false),594      } as unknown as Config;595      const loader = new FileCommandLoader(mockConfig);596      const commands = await loader.loadCommands(signal);597 598      expect(commands).toHaveLength(3);599      const commandNames = commands.map((cmd) => cmd.name);600      expect(commandNames).toEqual(['user', 'project', 'ext']);601 602      const extCommand = commands.find((cmd) => cmd.name === 'ext');603      expect(extCommand?.extensionName).toBe('test-ext');604      expect(extCommand?.description).toMatch(/^\[test-ext\]/);605    });606 607    it('extension commands have extensionName metadata for conflict resolution', async () => {608      const userCommandsDir = Storage.getUserCommandsDir();609      const projectCommandsDir = new Storage(610        process.cwd(),611      ).getProjectCommandsDir();612      const extensionDir = path.join(613        process.cwd(),614        '.qwen/extensions/test-ext',615      );616 617      mock({618        [extensionDir]: {619          'qwen-extension.json': JSON.stringify({620            name: 'test-ext',621            version: '1.0.0',622          }),623          commands: {624            'deploy.toml': 'prompt = "Extension deploy command"',625          },626        },627        [userCommandsDir]: {628          'deploy.toml': 'prompt = "User deploy command"',629        },630        [projectCommandsDir]: {631          'deploy.toml': 'prompt = "Project deploy command"',632        },633      });634 635      const mockConfig = {636        getProjectRoot: vi.fn(() => process.cwd()),637        getExtensions: vi.fn(() => [638          {639            name: 'test-ext',640            version: '1.0.0',641            isActive: true,642            path: extensionDir,643          },644        ]),645        getFolderTrustFeature: vi.fn(() => false),646        getFolderTrust: vi.fn(() => false),647      } as unknown as Config;648      const loader = new FileCommandLoader(mockConfig);649      const commands = await loader.loadCommands(signal);650 651      // Return all commands, even duplicates652      expect(commands).toHaveLength(3);653 654      expect(commands[0].name).toBe('deploy');655      expect(commands[0].extensionName).toBeUndefined();656      const result0 = await commands[0].action?.(657        createMockCommandContext({658          invocation: {659            raw: '/deploy',660            name: 'deploy',661            args: '',662          },663        }),664        '',665      );666      expect(result0?.type).toBe('submit_prompt');667      if (result0?.type === 'submit_prompt') {668        expect(result0.content).toEqual([{ text: 'User deploy command' }]);669      }670 671      expect(commands[1].name).toBe('deploy');672      expect(commands[1].extensionName).toBeUndefined();673      const result1 = await commands[1].action?.(674        createMockCommandContext({675          invocation: {676            raw: '/deploy',677            name: 'deploy',678            args: '',679          },680        }),681        '',682      );683      expect(result1?.type).toBe('submit_prompt');684      if (result1?.type === 'submit_prompt') {685        expect(result1.content).toEqual([{ text: 'Project deploy command' }]);686      }687 688      expect(commands[2].name).toBe('deploy');689      expect(commands[2].extensionName).toBe('test-ext');690      expect(commands[2].description).toMatch(/^\[test-ext\]/);691      const result2 = await commands[2].action?.(692        createMockCommandContext({693          invocation: {694            raw: '/test-ext.deploy',695            name: 'test-ext.deploy',696            args: '',697          },698        }),699        '',700      );701      expect(result2?.type).toBe('submit_prompt');702      if (result2?.type === 'submit_prompt') {703        expect(result2.content).toEqual([{ text: 'Extension deploy command' }]);704      }705    });706 707    it('only loads commands from active extensions', async () => {708      const extensionDir1 = path.join(709        process.cwd(),710        '.qwen/extensions/active-ext',711      );712      const extensionDir2 = path.join(713        process.cwd(),714        '.qwen/extensions/inactive-ext',715      );716 717      mock({718        [extensionDir1]: {719          'qwen-extension.json': JSON.stringify({720            name: 'active-ext',721            version: '1.0.0',722          }),723          commands: {724            'active.toml': 'prompt = "Active extension command"',725          },726        },727        [extensionDir2]: {728          'qwen-extension.json': JSON.stringify({729            name: 'inactive-ext',730            version: '1.0.0',731          }),732          commands: {733            'inactive.toml': 'prompt = "Inactive extension command"',734          },735        },736      });737 738      const mockConfig = {739        getProjectRoot: vi.fn(() => process.cwd()),740        getExtensions: vi.fn(() => [741          {742            name: 'active-ext',743            version: '1.0.0',744            isActive: true,745            path: extensionDir1,746          },747          {748            name: 'inactive-ext',749            version: '1.0.0',750            isActive: false,751            path: extensionDir2,752          },753        ]),754        getFolderTrustFeature: vi.fn(() => false),755        getFolderTrust: vi.fn(() => false),756      } as unknown as Config;757      const loader = new FileCommandLoader(mockConfig);758      const commands = await loader.loadCommands(signal);759 760      expect(commands).toHaveLength(1);761      expect(commands[0].name).toBe('active');762      expect(commands[0].extensionName).toBe('active-ext');763      expect(commands[0].description).toMatch(/^\[active-ext\]/);764    });765 766    it('handles missing extension commands directory gracefully', async () => {767      const extensionDir = path.join(768        process.cwd(),769        '.qwen/extensions/no-commands',770      );771 772      mock({773        [extensionDir]: {774          'qwen-extension.json': JSON.stringify({775            name: 'no-commands',776            version: '1.0.0',777          }),778          // No commands directory779        },780      });781 782      const mockConfig = {783        getProjectRoot: vi.fn(() => process.cwd()),784        getExtensions: vi.fn(() => [785          {786            name: 'no-commands',787            version: '1.0.0',788            isActive: true,789            path: extensionDir,790          },791        ]),792        getFolderTrustFeature: vi.fn(() => false),793        getFolderTrust: vi.fn(() => false),794      } as unknown as Config;795      const loader = new FileCommandLoader(mockConfig);796      const commands = await loader.loadCommands(signal);797      expect(commands).toHaveLength(0);798    });799 800    it('handles nested command structure in extensions', async () => {801      const extensionDir = path.join(process.cwd(), '.qwen/extensions/a');802 803      mock({804        [extensionDir]: {805          'qwen-extension.json': JSON.stringify({806            name: 'a',807            version: '1.0.0',808          }),809          commands: {810            b: {811              'c.toml': 'prompt = "Nested command from extension a"',812              d: {813                'e.toml': 'prompt = "Deeply nested command"',814              },815            },816            'simple.toml': 'prompt = "Simple command"',817          },818        },819      });820 821      const mockConfig = {822        getProjectRoot: vi.fn(() => process.cwd()),823        getExtensions: vi.fn(() => [824          { name: 'a', version: '1.0.0', isActive: true, path: extensionDir },825        ]),826        getFolderTrustFeature: vi.fn(() => false),827        getFolderTrust: vi.fn(() => false),828      } as unknown as Config;829      const loader = new FileCommandLoader(mockConfig);830      const commands = await loader.loadCommands(signal);831 832      expect(commands).toHaveLength(3);833 834      const commandNames = commands.map((cmd) => cmd.name).sort();835      expect(commandNames).toEqual(['b:c', 'b:d:e', 'simple']);836 837      const nestedCmd = commands.find((cmd) => cmd.name === 'b:c');838      expect(nestedCmd?.extensionName).toBe('a');839      expect(nestedCmd?.description).toMatch(/^\[a\]/);840      expect(nestedCmd).toBeDefined();841      const result = await nestedCmd!.action?.(842        createMockCommandContext({843          invocation: {844            raw: '/a.b:c',845            name: 'a.b:c',846            args: '',847          },848        }),849        '',850      );851      if (result?.type === 'submit_prompt') {852        expect(result.content).toEqual([853          { text: 'Nested command from extension a' },854        ]);855      } else {856        assert.fail('Incorrect action type');857      }858    });859  });860 861  describe('Argument Handling Integration (via ShellProcessor)', () => {862    it('correctly processes a command with {{args}}', async () => {863      const userCommandsDir = Storage.getUserCommandsDir();864      mock({865        [userCommandsDir]: {866          'shorthand.toml':867            'prompt = "The user wants to: {{args}}"\ndescription = "Shorthand test"',868        },869      });870 871      const loader = new FileCommandLoader(null as unknown as Config);872      const commands = await loader.loadCommands(signal);873      const command = commands.find((c) => c.name === 'shorthand');874      expect(command).toBeDefined();875 876      const result = await command!.action?.(877        createMockCommandContext({878          invocation: {879            raw: '/shorthand do something cool',880            name: 'shorthand',881            args: 'do something cool',882          },883        }),884        'do something cool',885      );886      expect(result?.type).toBe('submit_prompt');887      if (result?.type === 'submit_prompt') {888        expect(result.content).toEqual([889          { text: 'The user wants to: do something cool' },890        ]);891      }892    });893  });894 895  describe('Default Argument Processor Integration', () => {896    it('correctly processes a command without {{args}}', async () => {897      const userCommandsDir = Storage.getUserCommandsDir();898      mock({899        [userCommandsDir]: {900          'model_led.toml':901            'prompt = "This is the instruction."\ndescription = "Default processor test"',902        },903      });904 905      const loader = new FileCommandLoader(null as unknown as Config);906      const commands = await loader.loadCommands(signal);907      const command = commands.find((c) => c.name === 'model_led');908      expect(command).toBeDefined();909 910      const result = await command!.action?.(911        createMockCommandContext({912          invocation: {913            raw: '/model_led 1.2.0 added "a feature"',914            name: 'model_led',915            args: '1.2.0 added "a feature"',916          },917        }),918        '1.2.0 added "a feature"',919      );920      expect(result?.type).toBe('submit_prompt');921      if (result?.type === 'submit_prompt') {922        const expectedContent =923          'This is the instruction.\n\n/model_led 1.2.0 added "a feature"';924        expect(result.content).toEqual([{ text: expectedContent }]);925      }926    });927  });928 929  describe('Shell Processor Integration', () => {930    it('instantiates ShellProcessor if {{args}} is present (even without shell trigger)', async () => {931      const userCommandsDir = Storage.getUserCommandsDir();932      mock({933        [userCommandsDir]: {934          'args_only.toml': `prompt = "Hello {{args}}"`,935        },936      });937 938      const loader = new FileCommandLoader(null as unknown as Config);939      await loader.loadCommands(signal);940 941      expect(ShellProcessor).toHaveBeenCalledWith('args_only');942    });943    it('instantiates ShellProcessor if the trigger is present', async () => {944      const userCommandsDir = Storage.getUserCommandsDir();945      mock({946        [userCommandsDir]: {947          'shell.toml': `prompt = "Run this: ${SHELL_INJECTION_TRIGGER}echo hello}"`,948        },949      });950 951      const loader = new FileCommandLoader(null as unknown as Config);952      await loader.loadCommands(signal);953 954      expect(ShellProcessor).toHaveBeenCalledWith('shell');955    });956 957    it('does not instantiate ShellProcessor if no triggers ({{args}} or !{}) are present', async () => {958      const userCommandsDir = Storage.getUserCommandsDir();959      mock({960        [userCommandsDir]: {961          'regular.toml': `prompt = "Just a regular prompt"`,962        },963      });964 965      const loader = new FileCommandLoader(null as unknown as Config);966      await loader.loadCommands(signal);967 968      expect(ShellProcessor).not.toHaveBeenCalled();969    });970 971    it('returns a "submit_prompt" action if shell processing succeeds', async () => {972      const userCommandsDir = Storage.getUserCommandsDir();973      mock({974        [userCommandsDir]: {975          'shell.toml': `prompt = "Run !{echo 'hello'}"`,976        },977      });978      mockShellProcess.mockResolvedValue([{ text: 'Run hello' }]);979 980      const loader = new FileCommandLoader(null as unknown as Config);981      const commands = await loader.loadCommands(signal);982      const command = commands.find((c) => c.name === 'shell');983      expect(command).toBeDefined();984 985      const result = await command!.action!(986        createMockCommandContext({987          invocation: { raw: '/shell', name: 'shell', args: '' },988        }),989        '',990      );991 992      expect(result?.type).toBe('submit_prompt');993      if (result?.type === 'submit_prompt') {994        expect(result.content).toEqual([{ text: 'Run hello' }]);995      }996    });997 998    it('returns a "confirm_shell_commands" action if shell processing requires it', async () => {999      const userCommandsDir = Storage.getUserCommandsDir();1000      const rawInvocation = '/shell rm -rf /';1001      mock({1002        [userCommandsDir]: {1003          'shell.toml': `prompt = "Run !{rm -rf /}"`,1004        },1005      });1006 1007      // Mock the processor to throw the specific error1008      const error = new ConfirmationRequiredError('Confirmation needed', [1009        'rm -rf /',1010      ]);1011      mockShellProcess.mockRejectedValue(error);1012 1013      const loader = new FileCommandLoader(null as unknown as Config);1014      const commands = await loader.loadCommands(signal);1015      const command = commands.find((c) => c.name === 'shell');1016      expect(command).toBeDefined();1017 1018      const result = await command!.action!(1019        createMockCommandContext({1020          invocation: { raw: rawInvocation, name: 'shell', args: 'rm -rf /' },1021        }),1022        'rm -rf /',1023      );1024 1025      expect(result?.type).toBe('confirm_shell_commands');1026      if (result?.type === 'confirm_shell_commands') {1027        expect(result.commandsToConfirm).toEqual(['rm -rf /']);1028        expect(result.originalInvocation.raw).toBe(rawInvocation);1029      }1030    });1031 1032    it('re-throws other errors from the processor', async () => {1033      const userCommandsDir = Storage.getUserCommandsDir();1034      mock({1035        [userCommandsDir]: {1036          'shell.toml': `prompt = "Run !{something}"`,1037        },1038      });1039 1040      const genericError = new Error('Something else went wrong');1041      mockShellProcess.mockRejectedValue(genericError);1042 1043      const loader = new FileCommandLoader(null as unknown as Config);1044      const commands = await loader.loadCommands(signal);1045      const command = commands.find((c) => c.name === 'shell');1046      expect(command).toBeDefined();1047 1048      await expect(1049        command!.action!(1050          createMockCommandContext({1051            invocation: { raw: '/shell', name: 'shell', args: '' },1052          }),1053          '',1054        ),1055      ).rejects.toThrow('Something else went wrong');1056    });1057    it('assembles the processor pipeline in the correct order (AtFile -> Shell -> Default)', async () => {1058      const userCommandsDir = Storage.getUserCommandsDir();1059      mock({1060        [userCommandsDir]: {1061          // This prompt uses !{}, @{}, but NOT {{args}}, so all processors should be active.1062          'pipeline.toml': `1063              prompt = "Shell says: !{echo foo}. File says: @{./bar.txt}"1064            `,1065        },1066        './bar.txt': 'bar content',1067      });1068 1069      const defaultProcessMock = vi1070        .fn()1071        .mockImplementation((p: PromptPipelineContent) =>1072          Promise.resolve([1073            { text: `${(p[0] as { text: string }).text}-default-processed` },1074          ]),1075        );1076 1077      mockShellProcess.mockImplementation((p: PromptPipelineContent) =>1078        Promise.resolve([1079          { text: `${(p[0] as { text: string }).text}-shell-processed` },1080        ]),1081      );1082 1083      mockAtFileProcess.mockImplementation((p: PromptPipelineContent) =>1084        Promise.resolve([1085          { text: `${(p[0] as { text: string }).text}-at-file-processed` },1086        ]),1087      );1088 1089      vi.mocked(DefaultArgumentProcessor).mockImplementation(1090        () =>1091          ({1092            process: defaultProcessMock,1093          }) as unknown as DefaultArgumentProcessor,1094      );1095 1096      const loader = new FileCommandLoader(null as unknown as Config);1097      const commands = await loader.loadCommands(signal);1098      const command = commands.find((c) => c.name === 'pipeline');1099      expect(command).toBeDefined();1100 1101      const result = await command!.action!(1102        createMockCommandContext({1103          invocation: {1104            raw: '/pipeline baz',1105            name: 'pipeline',1106            args: 'baz',1107          },1108        }),1109        'baz',1110      );1111 1112      expect(mockAtFileProcess.mock.invocationCallOrder[0]).toBeLessThan(1113        mockShellProcess.mock.invocationCallOrder[0],1114      );1115      expect(mockShellProcess.mock.invocationCallOrder[0]).toBeLessThan(1116        defaultProcessMock.mock.invocationCallOrder[0],1117      );1118 1119      // Verify the flow of the prompt through the processors1120      // 1. AtFile processor runs first1121      expect(mockAtFileProcess).toHaveBeenCalledWith(1122        [{ text: expect.stringContaining('@{./bar.txt}') }],1123        expect.any(Object),1124      );1125      // 2. Shell processor runs second1126      expect(mockShellProcess).toHaveBeenCalledWith(1127        [{ text: expect.stringContaining('-at-file-processed') }],1128        expect.any(Object),1129      );1130      // 3. Default processor runs third1131      expect(defaultProcessMock).toHaveBeenCalledWith(1132        [{ text: expect.stringContaining('-shell-processed') }],1133        expect.any(Object),1134      );1135 1136      if (result?.type === 'submit_prompt') {1137        const contentAsArray = Array.isArray(result.content)1138          ? result.content1139          : [result.content];1140        expect(contentAsArray.length).toBeGreaterThan(0);1141        const firstPart = contentAsArray[0];1142 1143        if (typeof firstPart === 'object' && firstPart && 'text' in firstPart) {1144          expect(firstPart.text).toContain(1145            '-at-file-processed-shell-processed-default-processed',1146          );1147        } else {1148          assert.fail(1149            'First part of content is not a text part or is a string',1150          );1151        }1152      } else {1153        assert.fail('Incorrect action type');1154      }1155    });1156  });1157 1158  describe('@-file Processor Integration', () => {1159    it('correctly processes a command with @{file}', async () => {1160      const userCommandsDir = Storage.getUserCommandsDir();1161      mock({1162        [userCommandsDir]: {1163          'at-file.toml':1164            'prompt = "Context from file: @{./test.txt}"\ndescription = "@-file test"',1165        },1166        './test.txt': 'file content',1167      });1168 1169      mockAtFileProcess.mockImplementation(1170        async (prompt: PromptPipelineContent) => {1171          // A simplified mock of AtFileProcessor's behavior1172          const textContent = (prompt[0] as { text: string }).text;1173          if (textContent.includes('@{./test.txt}')) {1174            return [1175              {1176                text: textContent.replace('@{./test.txt}', 'file content'),1177              },1178            ];1179          }1180          return prompt;1181        },1182      );1183 1184      // Prevent default processor from interfering1185      vi.mocked(DefaultArgumentProcessor).mockImplementation(1186        () =>1187          ({1188            process: (p: PromptPipelineContent) => Promise.resolve(p),1189          }) as unknown as DefaultArgumentProcessor,1190      );1191 1192      const loader = new FileCommandLoader(null as unknown as Config);1193      const commands = await loader.loadCommands(signal);1194      const command = commands.find((c) => c.name === 'at-file');1195      expect(command).toBeDefined();1196 1197      const result = await command!.action?.(1198        createMockCommandContext({1199          invocation: {1200            raw: '/at-file',

Showing the first 1,200 of 1284 lines. Download the file for the rest.

basant307/AI_Governance_Project · CoolFace