diff --git a/src/agent/tools/plugin-loader.ts b/src/agent/tools/plugin-loader.ts index 176e3eca..0a2da090 100644 --- a/src/agent/tools/plugin-loader.ts +++ b/src/agent/tools/plugin-loader.ts @@ -46,7 +46,7 @@ import type { PluginMessageEvent, PluginCallbackEvent, } from "@teleton-agent/sdk"; -import { createLogger } from "../../utils/logger.js"; +import { createLogger, isVerbose } from "../../utils/logger.js"; const log = createLogger("PluginLoader"); @@ -159,19 +159,21 @@ export function adaptPlugin( debug: () => {}, }; const secretsCheck = createSecretsSDK(pluginName, pluginConfig, dummyLogger); - const missing: string[] = []; + let missingCount = 0; for (const [key, decl] of Object.entries( manifest.secrets as Record )) { if (decl.required && !secretsCheck.has(key)) { - missing.push(`${key} — ${decl.description}`); + missingCount++; + // Only log individual key names at debug level (requires /verbose) + pluginLog.debug(`Missing required secret for ${pluginName}`); } } - if (missing.length > 0) { + if (missingCount > 0) { + const detailHint = isVerbose() ? "" : " (enable /verbose for details)"; pluginLog.warn( - `Missing required secrets:\n` + - missing.map((m) => ` • ${m}`).join("\n") + - `\n Set via: /plugin set ${pluginName} ` + `Plugin "${pluginName}" has ${missingCount} missing required secret(s).` + + `${detailHint} Set via: /plugin set ${pluginName} ` ); } } diff --git a/src/sdk/__tests__/secrets.test.ts b/src/sdk/__tests__/secrets.test.ts index 8db33366..e4a3b1f2 100644 --- a/src/sdk/__tests__/secrets.test.ts +++ b/src/sdk/__tests__/secrets.test.ts @@ -79,7 +79,10 @@ describe("SecretsSDK resolution chain", () => { const sdk = createSecretsSDK("myplugin", { API_KEY: "from-config" }, mockLog); expect(sdk.get("API_KEY")).toBe("from-env"); - expect(mockLog.debug).toHaveBeenCalledWith(expect.stringContaining("env var")); + // Debug log should NOT contain key name or env var name + expect(mockLog.debug).toHaveBeenCalledWith(expect.stringContaining("myplugin")); + expect(mockLog.debug).not.toHaveBeenCalledWith(expect.stringContaining("API_KEY")); + expect(mockLog.debug).not.toHaveBeenCalledWith(expect.stringContaining("MYPLUGIN_API_KEY")); }); it("falls back to secrets file when no env var", () => { @@ -89,14 +92,16 @@ describe("SecretsSDK resolution chain", () => { const sdk = createSecretsSDK("myplugin", { API_KEY: "from-config" }, mockLog); expect(sdk.get("API_KEY")).toBe("from-file"); - expect(mockLog.debug).toHaveBeenCalledWith(expect.stringContaining("secrets store")); + expect(mockLog.debug).toHaveBeenCalledWith(expect.stringContaining("myplugin")); + expect(mockLog.debug).not.toHaveBeenCalledWith(expect.stringContaining("API_KEY")); }); it("falls back to pluginConfig when no env var and no file", () => { const sdk = createSecretsSDK("myplugin", { API_KEY: "from-config" }, mockLog); expect(sdk.get("API_KEY")).toBe("from-config"); - expect(mockLog.debug).toHaveBeenCalledWith(expect.stringContaining("pluginConfig")); + expect(mockLog.debug).toHaveBeenCalledWith(expect.stringContaining("myplugin")); + expect(mockLog.debug).not.toHaveBeenCalledWith(expect.stringContaining("API_KEY")); }); it("returns undefined when secret not found anywhere", () => { @@ -149,7 +154,9 @@ describe("SecretsSDK.require()", () => { } catch (err) { expect(err).toBeInstanceOf(PluginSDKError); expect((err as PluginSDKError).code).toBe("SECRET_NOT_FOUND"); - expect((err as PluginSDKError).message).toContain("MISSING_KEY"); + // Error message should NOT leak the key name + expect((err as PluginSDKError).message).not.toContain("MISSING_KEY"); + expect((err as PluginSDKError).message).toContain("myplugin"); expect((err as PluginSDKError).message).toContain("/plugin set"); } }); diff --git a/src/sdk/secrets.ts b/src/sdk/secrets.ts index dc862e83..a17cc947 100644 --- a/src/sdk/secrets.ts +++ b/src/sdk/secrets.ts @@ -80,21 +80,21 @@ export function createSecretsSDK( const envKey = `${envPrefix}_${key.toUpperCase()}`; const envValue = process.env[envKey]; if (envValue) { - log.debug(`Secret "${key}" resolved from env var ${envKey}`); + log.debug(`Secret resolved for ${pluginName}`); return envValue; } // 2. Persisted secrets store (set via /plugin set) const stored = readSecretsFile(pluginName); if (key in stored && stored[key]) { - log.debug(`Secret "${key}" resolved from secrets store`); + log.debug(`Secret resolved for ${pluginName}`); return stored[key]; } // 3. pluginConfig from config.yaml (legacy/manual) const configValue = pluginConfig[key]; if (configValue !== undefined && configValue !== null) { - log.debug(`Secret "${key}" resolved from pluginConfig`); + log.debug(`Secret resolved for ${pluginName}`); return String(configValue); } @@ -108,7 +108,7 @@ export function createSecretsSDK( const value = get(key); if (!value) { throw new PluginSDKError( - `Missing required secret "${key}". Set it via: /plugin set ${pluginName} ${key} `, + `Missing required secret for plugin "${pluginName}". Set it via: /plugin set ${pluginName} `, "SECRET_NOT_FOUND" ); }