mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-04-30 04:30:48 +00:00
Some models (e.g., glm-5.1) ignore the {{model}} template in code
blocks and write their own footer without the model name. Fix:
1. BundledSkillLoader prepends YOUR_MODEL_ID="glm-5.1" as a top-level
declaration at the start of the skill body — impossible to miss
2. SKILL.md references YOUR_MODEL_ID in footer instructions
3. Empty model → empty string (no "unknown" — prefer omission)
4. YOUR_MODEL_ID declaration only prepended when model is available
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
90 lines
2.8 KiB
TypeScript
90 lines
2.8 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright 2025 Qwen
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
import type { Config } from '@qwen-code/qwen-code-core';
|
|
import {
|
|
createDebugLogger,
|
|
appendToLastTextPart,
|
|
} from '@qwen-code/qwen-code-core';
|
|
import type { ICommandLoader } from './types.js';
|
|
import type {
|
|
SlashCommand,
|
|
SlashCommandActionReturn,
|
|
} from '../ui/commands/types.js';
|
|
import { CommandKind } from '../ui/commands/types.js';
|
|
|
|
const debugLogger = createDebugLogger('BUNDLED_SKILL_LOADER');
|
|
|
|
/**
|
|
* Loads bundled skills as slash commands, making them directly invocable
|
|
* via /<skill-name> (e.g., /review).
|
|
*/
|
|
export class BundledSkillLoader implements ICommandLoader {
|
|
constructor(private readonly config: Config | null) {}
|
|
|
|
async loadCommands(_signal: AbortSignal): Promise<SlashCommand[]> {
|
|
const skillManager = this.config?.getSkillManager();
|
|
if (!skillManager) {
|
|
debugLogger.debug('SkillManager not available, skipping bundled skills');
|
|
return [];
|
|
}
|
|
|
|
try {
|
|
const allSkills = await skillManager.listSkills({ level: 'bundled' });
|
|
|
|
// Hide skills whose allowedTools require cron when cron is disabled
|
|
const cronEnabled = this.config?.isCronEnabled() ?? false;
|
|
const skills = allSkills.filter((skill) => {
|
|
if (
|
|
!cronEnabled &&
|
|
skill.allowedTools?.some((t) => t.startsWith('cron_'))
|
|
) {
|
|
debugLogger.debug(
|
|
`Hiding skill "${skill.name}" because cron is not enabled`,
|
|
);
|
|
return false;
|
|
}
|
|
return true;
|
|
});
|
|
|
|
debugLogger.debug(
|
|
`Loaded ${skills.length} bundled skill(s) as slash commands`,
|
|
);
|
|
|
|
return skills.map((skill) => ({
|
|
name: skill.name,
|
|
description: skill.description,
|
|
kind: CommandKind.SKILL,
|
|
action: async (context, _args): Promise<SlashCommandActionReturn> => {
|
|
// Resolve template variables in skill body
|
|
let body = skill.body;
|
|
const modelId = this.config?.getModel()?.trim() || '';
|
|
if (body.includes('{{model}}') || body.includes('YOUR_MODEL_ID')) {
|
|
body = body.replaceAll('{{model}}', modelId);
|
|
body = body.replaceAll('YOUR_MODEL_ID', modelId);
|
|
// Prepend model identity as a top-level declaration so the LLM
|
|
// cannot miss it even if it doesn't copy the template exactly.
|
|
if (modelId) {
|
|
body = `YOUR_MODEL_ID="${modelId}"\n\n${body}`;
|
|
}
|
|
}
|
|
|
|
const content = context.invocation?.args
|
|
? appendToLastTextPart([{ text: body }], context.invocation.raw)
|
|
: [{ text: body }];
|
|
|
|
return {
|
|
type: 'submit_prompt',
|
|
content,
|
|
};
|
|
},
|
|
}));
|
|
} catch (error) {
|
|
debugLogger.error('Failed to load bundled skills:', error);
|
|
return [];
|
|
}
|
|
}
|
|
}
|