diff --git a/.changeset/polite-results-attend.md b/.changeset/polite-results-attend.md new file mode 100644 index 00000000000..3f782a5b7a1 --- /dev/null +++ b/.changeset/polite-results-attend.md @@ -0,0 +1,5 @@ +--- +"kilo-code": minor +--- + +Show notifications when skills are added or removed from the project or global config diff --git a/packages/types/src/context-config.ts b/packages/types/src/context-config.ts new file mode 100644 index 00000000000..18674f98a5b --- /dev/null +++ b/packages/types/src/context-config.ts @@ -0,0 +1,25 @@ +// kilocode_change - new file + +/** + * All discoverable configuration types that affect agent behavior. + * These are things that can be discovered from .kilocode directories. + */ +export type ContextConfigType = "skill" | "workflow" | "command" | "rule" | "mcp" + +/** + * Represents a single configuration change (added or removed). + */ +export interface ContextConfigChange { + /** What kind of configType this is */ + configType: ContextConfigType + /** Whether this item was added or removed */ + changeType: "added" | "removed" + /** Name/identifier of the item */ + name: string + /** Where was it discovered: global (~/.kilocode) or project (.kilocode) */ + source: "global" | "project" + /** Optional mode for mode-specific configs (e.g., 'code', 'architect') */ + mode?: string + /** Optional file path for "click to open" functionality */ + filePath?: string +} diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index 6806c4a8211..7a40d8b1c87 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -1,5 +1,6 @@ export * from "./api.js" export * from "./auto-purge.js" // kilocode_change +export * from "./context-config.js" // kilocode_change export * from "./cloud.js" export * from "./codebase-index.js" export * from "./context-management.js" diff --git a/src/i18n/locales/ar/kilocode.json b/src/i18n/locales/ar/kilocode.json index 43e412b29e0..27535117b51 100644 --- a/src/i18n/locales/ar/kilocode.json +++ b/src/i18n/locales/ar/kilocode.json @@ -207,5 +207,9 @@ "invalidApiKey": "مفتاح API الخاص بـ OpenAI غير صالح أو لا يملك صلاحية الوصول إلى Realtime API. يرجى التحقق من مفتاح API الخاص بك والمحاولة مرة أخرى.", "unknown": "فشل الاتصال. يرجى التحقق من الإعدادات والمحاولة مرة أخرى." } + }, + "configDiscovery": { + "added": "رمز كيلو: تم إضافة {{source}} {{configType}}: {{name}}{{modeStr}}", + "removed": "كود كيلوجرام: تمت إزالة {{source}} {{configType}}: {{name}}{{modeStr}}" } } diff --git a/src/i18n/locales/ca/kilocode.json b/src/i18n/locales/ca/kilocode.json index 1eb9d95bdb6..6da15b0bc88 100644 --- a/src/i18n/locales/ca/kilocode.json +++ b/src/i18n/locales/ca/kilocode.json @@ -203,5 +203,9 @@ "invalidApiKey": "La vostra clau de l'API d'OpenAI no és vàlida o no té accés a l'API en temps real. Si us plau, comproveu la vostra clau de l'API i torneu-ho a provar.", "unknown": "Ha fallat la connexió. Comproveu la configuració i torneu-ho a provar." } + }, + "configDiscovery": { + "removed": "Kilo Codi: Eliminat {{source}} {{configType}}: {{name}}{{modeStr}}", + "added": "Codi Kilo: Afegit {{source}} {{configType}}: {{name}}{{modeStr}}" } } diff --git a/src/i18n/locales/cs/kilocode.json b/src/i18n/locales/cs/kilocode.json index a841f508e0d..5778ec4c166 100644 --- a/src/i18n/locales/cs/kilocode.json +++ b/src/i18n/locales/cs/kilocode.json @@ -203,5 +203,9 @@ "invalidApiKey": "Váš OpenAI API klíč je neplatný nebo nemá přístup k Realtime API. Zkontrolujte prosím svůj API klíč a zkuste to znovu.", "unknown": "Připojení se nezdařilo. Zkontrolujte prosím svou konfiguraci a zkuste to znovu." } + }, + "configDiscovery": { + "added": "Kód kilo: Přidáno {{source}} {{configType}}: {{name}}{{modeStr}}", + "removed": "Kód Kilo: Odstraněno {{source}} {{configType}}: {{name}}{{modeStr}}" } } diff --git a/src/i18n/locales/de/kilocode.json b/src/i18n/locales/de/kilocode.json index 868174ffedb..904026042b8 100644 --- a/src/i18n/locales/de/kilocode.json +++ b/src/i18n/locales/de/kilocode.json @@ -203,5 +203,9 @@ "invalidApiKey": "Ihr OpenAI API-Schlüssel ist ungültig oder hat keinen Zugriff auf die Realtime API. Bitte überprüfen Sie Ihren API-Schlüssel und versuchen Sie es erneut.", "unknown": "Verbindung fehlgeschlagen. Bitte überprüfen Sie Ihre Konfiguration und versuchen Sie es erneut." } + }, + "configDiscovery": { + "added": "Kilo-Code: Hinzugefügt {{source}} {{configType}}: {{name}}{{modeStr}}", + "removed": "Kilo-Code: Entfernt {{source}} {{configType}}: {{name}}{{modeStr}}" } } diff --git a/src/i18n/locales/en/kilocode.json b/src/i18n/locales/en/kilocode.json index f6cdfbf4725..54ff064cc49 100644 --- a/src/i18n/locales/en/kilocode.json +++ b/src/i18n/locales/en/kilocode.json @@ -2,6 +2,10 @@ "info": { "settings_imported": "Settings imported successfully." }, + "configDiscovery": { + "added": "Kilo Code: Added {{source}} {{configType}}: {{name}}{{modeStr}}", + "removed": "Kilo Code: Removed {{source}} {{configType}}: {{name}}{{modeStr}}" + }, "userFeedback": { "message_update_failed": "Failed to update message", "no_checkpoint_found": "No checkpoint found before this message", diff --git a/src/i18n/locales/es/kilocode.json b/src/i18n/locales/es/kilocode.json index 3b4c6a3f8fe..ce597cbffd4 100644 --- a/src/i18n/locales/es/kilocode.json +++ b/src/i18n/locales/es/kilocode.json @@ -203,5 +203,9 @@ "invalidApiKey": "Tu clave de API de OpenAI es inválida o no tiene acceso a la API de Realtime. Por favor verifica tu clave de API e inténtalo de nuevo.", "unknown": "Error de conexión. Por favor, verifica tu configuración e inténtalo de nuevo." } + }, + "configDiscovery": { + "added": "Código Kilo: Se ha añadido {{source}} {{configType}}: {{name}}{{modeStr}}", + "removed": "Código Kilo: Removido {{source}} {{configType}}: {{name}}{{modeStr}}" } } diff --git a/src/i18n/locales/fr/kilocode.json b/src/i18n/locales/fr/kilocode.json index 8f43def10b6..cc2ab86dfac 100644 --- a/src/i18n/locales/fr/kilocode.json +++ b/src/i18n/locales/fr/kilocode.json @@ -203,5 +203,9 @@ "invalidApiKey": "Votre clé API OpenAI est invalide ou n'a pas accès à l'API Realtime. Veuillez vérifier votre clé API et réessayer.", "unknown": "Échec de la connexion. Veuillez vérifier votre configuration et réessayer." } + }, + "configDiscovery": { + "removed": "Code Kilo : Supprimé {{source}} {{configType}} : {{name}}{{modeStr}}", + "added": "Code Kilo : Ajouté {{source}} {{configType}} : {{name}}{{modeStr}}" } } diff --git a/src/i18n/locales/hi/kilocode.json b/src/i18n/locales/hi/kilocode.json index 793de46bc81..c80096cd205 100644 --- a/src/i18n/locales/hi/kilocode.json +++ b/src/i18n/locales/hi/kilocode.json @@ -203,5 +203,9 @@ "invalidApiKey": "आपकी OpenAI API key अमान्य है या इसमें Realtime API तक पहुंच नहीं है। कृपया अपनी API key जांचें और पुनः प्रयास करें।", "unknown": "कनेक्शन असफल हुआ। कृपया अपनी कॉन्फ़िगरेशन जांचें और पुनः प्रयास करें।" } + }, + "configDiscovery": { + "added": "किलो कोड: जोड़ा गया {{source}} {{configType}}: {{name}}{{modeStr}}", + "removed": "किलो कोड: हटाया गया {{source}} {{configType}}: {{name}}{{modeStr}}" } } diff --git a/src/i18n/locales/id/kilocode.json b/src/i18n/locales/id/kilocode.json index 663caead5af..3cc04401ec0 100644 --- a/src/i18n/locales/id/kilocode.json +++ b/src/i18n/locales/id/kilocode.json @@ -203,5 +203,9 @@ "invalidApiKey": "Kunci API OpenAI Anda tidak valid atau tidak memiliki akses ke Realtime API. Silakan periksa kunci API Anda dan coba lagi.", "unknown": "Koneksi gagal. Silakan periksa konfigurasi Anda dan coba lagi." } + }, + "configDiscovery": { + "added": "Kilo Kode: Ditambahkan {{source}} {{configType}}: {{name}}{{modeStr}}", + "removed": "Kilo Kode: Dihapus {{source}} {{configType}}: {{name}}{{modeStr}}" } } diff --git a/src/i18n/locales/it/kilocode.json b/src/i18n/locales/it/kilocode.json index 63b84073f97..298d0718eb6 100644 --- a/src/i18n/locales/it/kilocode.json +++ b/src/i18n/locales/it/kilocode.json @@ -203,5 +203,9 @@ "invalidApiKey": "La tua chiave API OpenAI non è valida o non ha accesso alla Realtime API. Controlla la tua chiave API e riprova.", "unknown": "Connessione fallita. Controlla la tua configurazione e riprova." } + }, + "configDiscovery": { + "removed": "Codice Kilo: Rimosso {{source}} {{configType}}: {{name}}{{modeStr}}", + "added": "Kilo Codice: Aggiunto {{source}} {{configType}}: {{name}}{{modeStr}}" } } diff --git a/src/i18n/locales/ja/kilocode.json b/src/i18n/locales/ja/kilocode.json index 4f4d9eaab04..398c2574c9a 100644 --- a/src/i18n/locales/ja/kilocode.json +++ b/src/i18n/locales/ja/kilocode.json @@ -203,5 +203,9 @@ "invalidApiKey": "OpenAI APIキーが無効であるか、Realtime APIへのアクセス権限がありません。APIキーを確認して再度お試しください。", "unknown": "接続に失敗しました。設定を確認してもう一度お試しください。" } + }, + "configDiscovery": { + "added": "Kiloコード: {{source}} {{configType}}: {{name}}{{modeStr}} を追加しました。", + "removed": "Kiloコード: {{source}} {{configType}}: {{name}}{{modeStr}} を削除しました" } } diff --git a/src/i18n/locales/ko/kilocode.json b/src/i18n/locales/ko/kilocode.json index 7154c42ee02..01cd86e2b55 100644 --- a/src/i18n/locales/ko/kilocode.json +++ b/src/i18n/locales/ko/kilocode.json @@ -203,5 +203,9 @@ "invalidApiKey": "OpenAI API 키가 유효하지 않거나 Realtime API에 대한 액세스 권한이 없습니다. API 키를 확인하고 다시 시도해 주세요.", "unknown": "연결에 실패했습니다. 설정을 확인한 후 다시 시도해 주세요." } + }, + "configDiscovery": { + "added": "킬로 코드: {{source}} {{configType}}: {{name}}{{modeStr}} 추가됨", + "removed": "Kilo 코드: 제거됨 {{source}} {{configType}}: {{name}}{{modeStr}}" } } diff --git a/src/i18n/locales/nl/kilocode.json b/src/i18n/locales/nl/kilocode.json index a40fcac1bed..f5a2dd042ea 100644 --- a/src/i18n/locales/nl/kilocode.json +++ b/src/i18n/locales/nl/kilocode.json @@ -203,5 +203,9 @@ "invalidApiKey": "Uw OpenAI API-sleutel is ongeldig of heeft geen toegang tot de Realtime API. Controleer uw API-sleutel en probeer het opnieuw.", "unknown": "Verbinding mislukt. Controleer uw configuratie en probeer het opnieuw." } + }, + "configDiscovery": { + "added": "Kilo Code: Toegevoegd {{source}} {{configType}}: {{name}}{{modeStr}}", + "removed": "Kilo code: Verwijderd {{source}} {{configType}}: {{name}}{{modeStr}}" } } diff --git a/src/i18n/locales/pl/kilocode.json b/src/i18n/locales/pl/kilocode.json index 6948701816b..df173b10c77 100644 --- a/src/i18n/locales/pl/kilocode.json +++ b/src/i18n/locales/pl/kilocode.json @@ -203,5 +203,9 @@ "invalidApiKey": "Twój klucz API OpenAI jest nieprawidłowy lub nie ma dostępu do API Realtime. Sprawdź swój klucz API i spróbuj ponownie.", "unknown": "Połączenie nie powiodło się. Sprawdź konfigurację i spróbuj ponownie." } + }, + "configDiscovery": { + "added": "Kilo Code: Dodano {{source}} {{configType}}: {{name}}{{modeStr}}", + "removed": "Kilo Kod: Usunięto {{source}} {{configType}}: {{name}}{{modeStr}}" } } diff --git a/src/i18n/locales/pt-BR/kilocode.json b/src/i18n/locales/pt-BR/kilocode.json index 984478229fc..9d57c3c8f3f 100644 --- a/src/i18n/locales/pt-BR/kilocode.json +++ b/src/i18n/locales/pt-BR/kilocode.json @@ -203,5 +203,9 @@ "invalidApiKey": "Sua chave da API OpenAI é inválida ou não tem acesso à API Realtime. Por favor, verifique sua chave da API e tente novamente.", "unknown": "Falha na conexão. Verifique sua configuração e tente novamente." } + }, + "configDiscovery": { + "added": "Código Kilo: Adicionado {{source}} {{configType}}: {{name}}{{modeStr}}", + "removed": "Código Kilo: Removido {{source}} {{configType}}: {{name}}{{modeStr}}" } } diff --git a/src/i18n/locales/ru/kilocode.json b/src/i18n/locales/ru/kilocode.json index 1ed5daddee9..5b2b2e5fe63 100644 --- a/src/i18n/locales/ru/kilocode.json +++ b/src/i18n/locales/ru/kilocode.json @@ -203,5 +203,9 @@ "invalidApiKey": "Ваш API-ключ OpenAI недействителен или не имеет доступа к Realtime API. Пожалуйста, проверьте ваш API-ключ и попробуйте снова.", "unknown": "Подключение не удалось. Пожалуйста, проверьте настройки и повторите попытку." } + }, + "configDiscovery": { + "added": "Код Кило: Добавлен {{source}} {{configType}}: {{name}}{{modeStr}}", + "removed": "Код Kilo: Удалено {{source}} {{configType}}: {{name}}{{modeStr}}" } } diff --git a/src/i18n/locales/th/kilocode.json b/src/i18n/locales/th/kilocode.json index fbbcc990d01..f8cd197ed24 100644 --- a/src/i18n/locales/th/kilocode.json +++ b/src/i18n/locales/th/kilocode.json @@ -203,5 +203,9 @@ "invalidApiKey": "คีย์ OpenAI API ของคุณไม่ถูกต้องหรือไม่มีสิทธิ์เข้าถึง Realtime API กรุณาตรวจสอบคีย์ API ของคุณและลองใหม่อีกครั้ง", "unknown": "การเชื่อมต่อล้มเหลว กรุณาตรวจสอบการกำหนดค่าของคุณและลองใหม่อีกครั้ง" } + }, + "configDiscovery": { + "added": "Kilo Code: เพิ่ม {{source}} {{configType}}: {{name}}{{modeStr}}", + "removed": "Kilo Code: ลบ {{source}} {{configType}}: {{name}}{{modeStr}}" } } diff --git a/src/i18n/locales/tr/kilocode.json b/src/i18n/locales/tr/kilocode.json index d7e8f736230..340a12fd434 100644 --- a/src/i18n/locales/tr/kilocode.json +++ b/src/i18n/locales/tr/kilocode.json @@ -203,5 +203,9 @@ "invalidApiKey": "OpenAI API anahtarınız geçersiz veya Realtime API'ye erişim izni bulunmuyor. Lütfen API anahtarınızı kontrol edin ve tekrar deneyin.", "unknown": "Bağlantı başarısız oldu. Lütfen yapılandırmanızı kontrol edin ve tekrar deneyin." } + }, + "configDiscovery": { + "added": "Kilo Kodu: {{source}} {{configType}}: {{name}}{{modeStr}} eklendi.", + "removed": "Kilo Kodu: Kaldırıldı {{source}} {{configType}}: {{name}}{{modeStr}}" } } diff --git a/src/i18n/locales/uk/kilocode.json b/src/i18n/locales/uk/kilocode.json index d08f4ac98cd..586abd6816b 100644 --- a/src/i18n/locales/uk/kilocode.json +++ b/src/i18n/locales/uk/kilocode.json @@ -203,5 +203,9 @@ "invalidApiKey": "Ваш API ключ OpenAI недійсний або не має доступу до Realtime API. Будь ласка, перевірте ваш API ключ і спробуйте знову.", "unknown": "Підключення не вдалося. Будь ласка, перевірте налаштування та спробуйте ще раз." } + }, + "configDiscovery": { + "removed": "Код одиниці: Видалено {{source}} {{configType}}: {{name}}{{modeStr}}", + "added": "Код Кіло: Додано {{source}} {{configType}}: {{name}}{{modeStr}}" } } diff --git a/src/i18n/locales/vi/kilocode.json b/src/i18n/locales/vi/kilocode.json index 78a755fc2c5..bfbcfc4a374 100644 --- a/src/i18n/locales/vi/kilocode.json +++ b/src/i18n/locales/vi/kilocode.json @@ -203,5 +203,9 @@ "invalidApiKey": "Khóa API OpenAI của bạn không hợp lệ hoặc không có quyền truy cập vào Realtime API. Vui lòng kiểm tra khóa API và thử lại.", "unknown": "Kết nối thất bại. Vui lòng kiểm tra cấu hình của bạn và thử lại." } + }, + "configDiscovery": { + "added": "Mã Kilo: Đã thêm {{source}} {{configType}}: {{name}}{{modeStr}}", + "removed": "Mã Kilo: Đã xóa {{source}} {{configType}}: {{name}}{{modeStr}}" } } diff --git a/src/i18n/locales/zh-CN/kilocode.json b/src/i18n/locales/zh-CN/kilocode.json index c7cf972e53d..c316353a65e 100644 --- a/src/i18n/locales/zh-CN/kilocode.json +++ b/src/i18n/locales/zh-CN/kilocode.json @@ -203,5 +203,9 @@ "invalidApiKey": "您的 OpenAI API 密钥无效或无权访问实时 API。请检查您的 API 密钥并重试。", "unknown": "连接失败。请检查您的配置并重试。" } + }, + "configDiscovery": { + "added": "Kilo 代码:添加了{{source}} {{configType}}:{{name}}{{modeStr}}", + "removed": "Kilo代码:已移除{{source}} {{configType}}:{{name}}{{modeStr}}" } } diff --git a/src/i18n/locales/zh-TW/kilocode.json b/src/i18n/locales/zh-TW/kilocode.json index fef29cee07a..d7bb4665067 100644 --- a/src/i18n/locales/zh-TW/kilocode.json +++ b/src/i18n/locales/zh-TW/kilocode.json @@ -203,5 +203,9 @@ "invalidApiKey": "您的 OpenAI API 密钥无效或没有访问 Realtime API 的权限。请检查您的 API 密钥并重试。", "unknown": "连接失败。请检查您的配置并重试。" } + }, + "configDiscovery": { + "removed": "Kilo代码:已删除{{source}}{{configType}}:{{name}}{{modeStr}}", + "added": "Kilo代码:已添加{{source}} {{configType}}:{{name}}{{modeStr}}" } } diff --git a/src/services/config/ConfigChangeNotifier.spec.ts b/src/services/config/ConfigChangeNotifier.spec.ts new file mode 100644 index 00000000000..dbbf84c2d8c --- /dev/null +++ b/src/services/config/ConfigChangeNotifier.spec.ts @@ -0,0 +1,64 @@ +// kilocode_change - new file + +import { describe, it, expect, vi, beforeEach } from "vitest" +import type { ClineProvider } from "../../core/webview/ClineProvider" +import { ConfigChangeNotifier } from "./ConfigChangeNotifier" + +const { mockShowInformationMessage } = vi.hoisted(() => ({ + mockShowInformationMessage: vi.fn(), +})) + +vi.mock("vscode", () => ({ + window: { showInformationMessage: mockShowInformationMessage }, +})) + +vi.mock("../../i18n", () => ({ + t: vi.fn((key, vars) => `${key} ${JSON.stringify(vars)}`), +})) + +describe("ConfigChangeNotifier", () => { + let mockProvider: ClineProvider + let notifier: ConfigChangeNotifier + + beforeEach(() => { + vi.clearAllMocks() + mockProvider = { cwd: "/test" } as unknown as ClineProvider + notifier = new ConfigChangeNotifier(mockProvider) + }) + + it("should skip initial discovery without notifying", async () => { + await notifier.notifyIfChanged("skill", [{ name: "test-skill", source: "global" }]) + expect(mockShowInformationMessage).not.toHaveBeenCalled() + }) + + it("should detect added configurations", async () => { + await notifier.notifyIfChanged("skill", [{ name: "existing-skill", source: "global" }]) + await notifier.notifyIfChanged("skill", [ + { name: "existing-skill", source: "global" }, + { name: "new-skill", source: "global" }, + ]) + expect(mockShowInformationMessage).toHaveBeenCalledTimes(1) + expect(mockShowInformationMessage).toHaveBeenCalledWith(expect.stringContaining("new-skill")) + }) + + it("should detect removed configurations", async () => { + await notifier.notifyIfChanged("skill", [ + { name: "existing-skill", source: "global" }, + { name: "removed-skill", source: "global" }, + ]) + await notifier.notifyIfChanged("skill", [{ name: "existing-skill", source: "global" }]) + expect(mockShowInformationMessage).toHaveBeenCalledTimes(1) + expect(mockShowInformationMessage).toHaveBeenCalledWith(expect.stringContaining("removed-skill")) + }) + + it("should track different config types separately", async () => { + await notifier.notifyIfChanged("skill", [{ name: "skill-a", source: "global" }]) + await notifier.notifyIfChanged("workflow", [{ name: "workflow-a", source: "global" }]) + await notifier.notifyIfChanged("skill", [ + { name: "skill-a", source: "global" }, + { name: "skill-b", source: "global" }, + ]) + expect(mockShowInformationMessage).toHaveBeenCalledTimes(1) + expect(mockShowInformationMessage).toHaveBeenCalledWith(expect.stringContaining("skill-b")) + }) +}) diff --git a/src/services/config/ConfigChangeNotifier.ts b/src/services/config/ConfigChangeNotifier.ts new file mode 100644 index 00000000000..ae2ecfa783d --- /dev/null +++ b/src/services/config/ConfigChangeNotifier.ts @@ -0,0 +1,96 @@ +// kilocode_change - new file + +import * as vscode from "vscode" +import type { ClineProvider } from "../../core/webview/ClineProvider" +import type { ContextConfigChange, ContextConfigType } from "@roo-code/types" +import { t } from "../../i18n" + +interface ConfigInput { + name: string + source: "global" | "project" + mode?: string + path?: string +} + +export class ConfigChangeNotifier { + private providerRef: WeakRef + private previousKeys = new Map>() + + constructor(provider: ClineProvider) { + this.providerRef = new WeakRef(provider) + } + + async notifyIfChanged(configType: ContextConfigType, currentConfigs: ConfigInput[]): Promise { + const currentKeys = new Set(currentConfigs.map((c) => `${c.source}:${c.mode || ""}:${c.name}`)) + + if (!this.previousKeys.has(configType)) { + this.previousKeys.set(configType, currentKeys) + return + } + + const previousKeys = this.previousKeys.get(configType)! + const changes = this.detectChanges(previousKeys, currentKeys, currentConfigs, configType) + + this.previousKeys.set(configType, currentKeys) + + if (changes.length > 0) { + await this.showNotifications(changes) + } + } + + private detectChanges( + previousKeys: Set, + currentKeys: Set, + currentConfigs: ConfigInput[], + configType: ContextConfigType, + ): ContextConfigChange[] { + const changes: ContextConfigChange[] = [] + const configsByKey = new Map(currentConfigs.map((c) => [`${c.source}:${c.mode || ""}:${c.name}`, c])) + + for (const key of currentKeys) { + if (!previousKeys.has(key)) { + const config = configsByKey.get(key)! + changes.push({ + configType, + changeType: "added", + name: config.name, + source: config.source, + mode: config.mode, + filePath: config.path, + }) + } + } + + for (const key of previousKeys) { + if (!currentKeys.has(key)) { + const parts = key.split(":") + changes.push({ + configType, + changeType: "removed", + name: parts.slice(2).join(":"), + source: parts[0] as "global" | "project", + mode: parts[1] || undefined, + }) + } + } + + return changes + } + + private async showNotifications(changes: ContextConfigChange[]): Promise { + const provider = this.providerRef.deref() + if (!provider) return + + for (const change of changes) { + vscode.window.showInformationMessage(this.formatMessage(change)) + } + } + + private formatMessage(change: ContextConfigChange): string { + const modeStr = change.mode ? ` (${change.mode} mode)` : "" + const sourceStr = change.source === "global" ? "global" : "project" + const key = + change.changeType === "added" ? "kilocode:configDiscovery.added" : "kilocode:configDiscovery.removed" + return t(key, { configType: change.configType, name: change.name, source: sourceStr, modeStr }) + } +} diff --git a/src/services/skills/SkillsManager.ts b/src/services/skills/SkillsManager.ts index fd616df6a6f..35577f5e096 100644 --- a/src/services/skills/SkillsManager.ts +++ b/src/services/skills/SkillsManager.ts @@ -8,6 +8,7 @@ import { getGlobalRooDirectory } from "../roo-config" import { directoryExists, fileExists } from "../roo-config" import { SkillMetadata, SkillContent } from "../../shared/skills" import { modes, getAllModes } from "../../shared/modes" +import { ConfigChangeNotifier } from "../config/ConfigChangeNotifier" // kilocode_change // Re-export for convenience export type { SkillMetadata, SkillContent } @@ -17,9 +18,11 @@ export class SkillsManager { private providerRef: WeakRef private disposables: vscode.Disposable[] = [] private isDisposed = false + private configChangeNotifier: ConfigChangeNotifier // kilocode_change constructor(provider: ClineProvider) { this.providerRef = new WeakRef(provider) + this.configChangeNotifier = new ConfigChangeNotifier(provider) // kilocode_change } async initialize(): Promise { @@ -41,6 +44,9 @@ export class SkillsManager { for (const { dir, source, mode } of skillsDirs) { await this.scanSkillsDirectory(dir, source, mode) } + + const currentSkills = Array.from(this.skills.values()) // kilocode_change + await this.configChangeNotifier.notifyIfChanged("skill", currentSkills) // kilocode_change } /** @@ -299,7 +305,11 @@ export class SkillsManager { return } - const pattern = new vscode.RelativePattern(dirPath, "**/SKILL.md") + // kilocode_change start + // Watch for direct children (skill directories) being added/changed/deleted + // When anything changes, we'll rescan and look for SKILL.md files + const pattern = new vscode.RelativePattern(dirPath, "*") + // kilocode_change end const watcher = vscode.workspace.createFileSystemWatcher(pattern) watcher.onDidChange(async (uri) => {