From b200e5b91b742c37b746db4eeee32019a18c8075 Mon Sep 17 00:00:00 2001
From: dong <2970752254@qq.com>
Date: Tue, 22 Sep 2026 11:44:43 +0800
Subject: [PATCH] fix(frontend): show empty skill export requirements (#5659)
---
.../capabilities/skill-export-dialog.tsx | 23 ++++++---
frontend/src/core/i18n/locales/en-US.ts | 1 +
frontend/src/core/i18n/locales/types.ts | 1 +
frontend/src/core/i18n/locales/zh-CN.ts | 1 +
.../skill-export-dialog.dom.test.tsx | 49 +++++++++++++++++++
5 files changed, 67 insertions(+), 8 deletions(-)
diff --git a/frontend/src/components/workspace/capabilities/skill-export-dialog.tsx b/frontend/src/components/workspace/capabilities/skill-export-dialog.tsx
index 91b034543..48c7d3c18 100644
--- a/frontend/src/components/workspace/capabilities/skill-export-dialog.tsx
+++ b/frontend/src/components/workspace/capabilities/skill-export-dialog.tsx
@@ -217,8 +217,11 @@ export default function SkillExportDialog({
{text.exportTools}
- {manifest.requirements.allowed_tools?.join(", ") ??
- text.exportUndeclared}
+ {manifest.requirements.allowed_tools === null
+ ? text.exportUndeclared
+ : manifest.requirements.allowed_tools.length === 0
+ ? text.exportNone
+ : manifest.requirements.allowed_tools.join(", ")}
@@ -226,12 +229,16 @@ export default function SkillExportDialog({
{text.exportSecrets}
- {manifest.requirements.required_secrets
- ?.map(
- (secret) =>
- `${secret.name} (${secret.optional ? text.exportOptional : text.exportRequired})`,
- )
- .join(", ") ?? text.exportUndeclared}
+ {manifest.requirements.required_secrets === null
+ ? text.exportUndeclared
+ : manifest.requirements.required_secrets.length === 0
+ ? text.exportNone
+ : manifest.requirements.required_secrets
+ .map(
+ (secret) =>
+ `${secret.name} (${secret.optional ? text.exportOptional : text.exportRequired})`,
+ )
+ .join(", ")}
diff --git a/frontend/src/core/i18n/locales/en-US.ts b/frontend/src/core/i18n/locales/en-US.ts
index 93cc4e056..1834bbacd 100644
--- a/frontend/src/core/i18n/locales/en-US.ts
+++ b/frontend/src/core/i18n/locales/en-US.ts
@@ -1792,6 +1792,7 @@ export const enUS: Translations = {
exportOptional: "optional",
exportRequired: "required",
exportUndeclared: "Not declared",
+ exportNone: "None",
exportScope:
"Includes all files inside this skill. Account settings, conversations and history outside the skill folder are excluded. Configure tools and credentials again on the destination.",
exportWarnings: "Check package contents",
diff --git a/frontend/src/core/i18n/locales/types.ts b/frontend/src/core/i18n/locales/types.ts
index b2f0960bb..ca7fa52f4 100644
--- a/frontend/src/core/i18n/locales/types.ts
+++ b/frontend/src/core/i18n/locales/types.ts
@@ -1434,6 +1434,7 @@ export interface Translations {
exportOptional: string;
exportRequired: string;
exportUndeclared: string;
+ exportNone: string;
exportScope: string;
exportWarnings: string;
exportWarningDescription: string;
diff --git a/frontend/src/core/i18n/locales/zh-CN.ts b/frontend/src/core/i18n/locales/zh-CN.ts
index e3e964338..0763b697a 100644
--- a/frontend/src/core/i18n/locales/zh-CN.ts
+++ b/frontend/src/core/i18n/locales/zh-CN.ts
@@ -1683,6 +1683,7 @@ export const zhCN: Translations = {
exportOptional: "可选",
exportRequired: "必需",
exportUndeclared: "未声明",
+ exportNone: "无",
exportScope:
"包含此技能目录内的全部文件。账号配置、对话和目录外的历史不会导出;目标环境需重新配置工具与凭据。",
exportWarnings: "请检查包内文件",
diff --git a/frontend/tests/unit/components/workspace/capabilities/skill-export-dialog.dom.test.tsx b/frontend/tests/unit/components/workspace/capabilities/skill-export-dialog.dom.test.tsx
index 6346f0340..09f4350df 100644
--- a/frontend/tests/unit/components/workspace/capabilities/skill-export-dialog.dom.test.tsx
+++ b/frontend/tests/unit/components/workspace/capabilities/skill-export-dialog.dom.test.tsx
@@ -66,6 +66,55 @@ beforeEach(() => {
afterEach(cleanup);
describe("export dialog lifecycle", () => {
+ it.each([
+ {
+ name: "undeclared",
+ allowedTools: null,
+ requiredSecrets: null,
+ expectedTools: "Not declared",
+ expectedSecrets: "Not declared",
+ },
+ {
+ name: "explicitly empty",
+ allowedTools: [],
+ requiredSecrets: [],
+ expectedTools: "None",
+ expectedSecrets: "None",
+ },
+ {
+ name: "declared values",
+ allowedTools: ["read_file"],
+ requiredSecrets: [{ name: "API_KEY", optional: false }],
+ expectedTools: "read_file",
+ expectedSecrets: "API_KEY (required)",
+ },
+ ])(
+ "renders $name requirements",
+ async ({
+ allowedTools,
+ requiredSecrets,
+ expectedTools,
+ expectedSecrets,
+ }) => {
+ mocks.load.mockResolvedValue({
+ ...manifest,
+ requirements: {
+ ...manifest.requirements,
+ allowed_tools: allowedTools,
+ required_secrets: requiredSecrets,
+ },
+ });
+ render();
+ await screen.findByText("Declared requirements");
+ expect(
+ screen.getByText("Allowed tools").nextElementSibling?.textContent,
+ ).toBe(expectedTools);
+ expect(
+ screen.getByText("Credential names").nextElementSibling?.textContent,
+ ).toBe(expectedSecrets);
+ },
+ );
+
it("pages the file list and distinguishes undeclared dependencies", async () => {
render();
await screen.findByText("DEMO_KEY (optional)");