CoolFace
Datasetpublic

basant307/AI_Governance_Project

sourceHugging Faceapache-2.0updated 2mo agoView on Hugging Face
0likes45downloads
saved-workflow-loader.ts113 linesDownload Raw Back to services
1/**2 * @license3 * Copyright 2026 Qwen Team4 * SPDX-License-Identifier: Apache-2.05 */6 7/**8 * @fileoverview Discovers saved workflow scripts under `.qwen/workflows/`9 * (project) and `~/.qwen/workflows/` (user) and exposes each as a `/<name>`10 * slash command that dispatches the `workflow` tool with the file's path.11 * The script is read at execution time (by the tool), so edits to a saved12 * workflow take effect on the next invocation.13 *14 * Enumeration, project-over-user precedence, and the name constraint all live15 * in core's `listSavedWorkflows` — the single source of truth shared with the16 * `workflow('<name>')` in-script global. This loader only adapts the17 * discovered entries into `SlashCommand` objects.18 */19 20import type { Config, SavedWorkflowEntry } from '@qwen-code/qwen-code-core';21import {22  listSavedWorkflows,23  ToolNames,24  createDebugLogger,25} from '@qwen-code/qwen-code-core';26import type { ICommandLoader } from './types.js';27import type {28  CommandContext,29  SlashCommand,30  SlashCommandActionReturn,31} from '../ui/commands/types.js';32import { CommandKind } from '../ui/commands/types.js';33 34const debugLogger = createDebugLogger('SavedWorkflowLoader');35 36export class SavedWorkflowLoader implements ICommandLoader {37  constructor(private readonly config: Config | null) {}38 39  async loadCommands(signal: AbortSignal): Promise<SlashCommand[]> {40    if (!this.config) return [];41    // Feature gate: the `workflow` tool is only registered when the feature42    // flag is on, so without this guard the commands would dispatch a tool43    // that doesn't exist.44    if (!this.config.isWorkflowsEnabled?.()) return [];45    // Mirror FileCommandLoader: saved workflows execute project-local code, so46    // skip discovery in bare mode and in untrusted folders.47    if (this.config.getBareMode?.()) return [];48    const folderTrustEnabled = !!this.config.getFolderTrustFeature?.();49    const folderTrust = !!this.config.getFolderTrust?.();50    if (folderTrustEnabled && !folderTrust) return [];51 52    let entries: SavedWorkflowEntry[];53    try {54      entries = await listSavedWorkflows(this.config);55    } catch (e) {56      debugLogger.debug(`listSavedWorkflows failed: ${e}`);57      return [];58    }59    if (signal.aborted) return [];60    return entries.map((entry) => this.toCommand(entry));61  }62 63  private toCommand(entry: SavedWorkflowEntry): SlashCommand {64    return {65      name: entry.name,66      description: `Run the "${entry.name}" saved workflow (${entry.source})`,67      // File-derived command (all execution modes via commandUtils fallback);68      // `source` carries the distinct workflow identity for display/telemetry.69      kind: CommandKind.FILE,70      source: 'workflow-command',71      sourceLabel: 'Workflow',72      sourceDetail: entry.source, // 'project' | 'user'73      // Interactive only: the action returns a `{type:'tool'}` dispatch, which74      // the non-interactive command adapter converts to `unsupported`. Listing75      // these in headless / ACP modes would advertise a command that then fails76      // to run, so restrict them until those paths can execute a tool return.77      supportedModes: ['interactive'],78      acceptsInput: true,79      argumentHint: '[json-args]',80      action: (81        _context: CommandContext,82        args: string,83      ): SlashCommandActionReturn => {84        const toolArgs: Record<string, unknown> = {85          // The tool reads the file fresh at execution time (hot reload).86          scriptPath: entry.scriptPath,87        };88        const trimmed = (args ?? '').trim();89        if (trimmed.length > 0) {90          // Forward user-supplied text to the script's `args` global. Parse as91          // JSON when valid (objects / arrays / numbers — matching the tool's92          // "actual JSON value" contract), else pass the raw string so plain93          // text still reaches the script.94          toolArgs['args'] = tryParseJson(trimmed);95        }96        return {97          type: 'tool',98          toolName: ToolNames.WORKFLOW,99          toolArgs,100        };101      },102    };103  }104}105 106function tryParseJson(raw: string): unknown {107  try {108    return JSON.parse(raw);109  } catch {110    return raw;111  }112}113 
basant307/AI_Governance_Project · CoolFace