Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 9 additions & 7 deletions src/agent/tools/plugin-loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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");

Expand Down Expand Up @@ -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<string, SecretDeclaration>
)) {
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} <key> <value>`
`Plugin "${pluginName}" has ${missingCount} missing required secret(s).` +
`${detailHint} Set via: /plugin set ${pluginName} <key> <value>`
);
}
}
Expand Down
15 changes: 11 additions & 4 deletions src/sdk/__tests__/secrets.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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", () => {
Expand All @@ -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", () => {
Expand Down Expand Up @@ -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");
}
});
Expand Down
8 changes: 4 additions & 4 deletions src/sdk/secrets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand All @@ -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} <value>`,
`Missing required secret for plugin "${pluginName}". Set it via: /plugin set ${pluginName} <key> <value>`,
"SECRET_NOT_FOUND"
);
}
Expand Down
Loading