feat: implement Tier 3 features — lane queue, credential redaction, token dashboard, xAI, Voyage AI
- Lane Queue: per-session FIFO queue in gateway replacing reject-when-busy (9 tests) - Credential Redaction: redactConfig() expanded to cover 18+ secret fields (16 tests) - Web UI Token Dashboard: system.tokenUsage endpoint + Usage page with summary cards - xAI (Grok) Provider: OpenAI-compatible client with model pricing - Voyage AI Embeddings: new embedding provider with configurable dimensions (5 tests) - Update gap analysis: 90→95 match (70%→74%), Tier 3 section marked DONE - Update state.json: test count 1001→1034, add tier3_completion entry Total: 1034 tests passing across 85 files, typecheck clean
This commit is contained in:
@@ -8,30 +8,90 @@ export interface ConfigHandlerDeps {
|
||||
|
||||
/**
|
||||
* Redact sensitive values from config before returning.
|
||||
* Replaces API keys, tokens, and passwords with "***".
|
||||
* Replaces API keys, tokens, passwords, and other credentials with "***".
|
||||
*
|
||||
* Covers: telegram, discord, slack, server, models (tiers + fallbacks + local_providers),
|
||||
* web_search, audio, memory.embedding, automation (webhooks + gmail), and mcp server env vars.
|
||||
*/
|
||||
function redactConfig(config: Config): Record<string, unknown> {
|
||||
export function redactConfig(config: Config): Record<string, unknown> {
|
||||
const raw = JSON.parse(JSON.stringify(config)) as Record<string, unknown>;
|
||||
|
||||
// Redact telegram bot token
|
||||
const telegram = raw.telegram as Record<string, unknown> | undefined;
|
||||
if (telegram?.bot_token) {
|
||||
telegram.bot_token = '***';
|
||||
}
|
||||
// Helper: redact specified keys on an object if they exist and are non-nullish
|
||||
const redact = (obj: Record<string, unknown> | undefined, ...keys: string[]) => {
|
||||
if (!obj) return;
|
||||
for (const key of keys) {
|
||||
if (obj[key] !== undefined && obj[key] !== null) obj[key] = '***';
|
||||
}
|
||||
};
|
||||
|
||||
// Redact model keys/tokens
|
||||
// Telegram
|
||||
redact(raw.telegram as Record<string, unknown>, 'bot_token');
|
||||
|
||||
// Discord
|
||||
redact(raw.discord as Record<string, unknown>, 'bot_token');
|
||||
|
||||
// Slack
|
||||
redact(raw.slack as Record<string, unknown>, 'bot_token', 'app_token', 'signing_secret');
|
||||
|
||||
// Server (gateway bearer token)
|
||||
redact(raw.server as Record<string, unknown>, 'token');
|
||||
|
||||
// Models — tiers, their fallbacks, and local_providers (+ their fallbacks)
|
||||
const models = raw.models as Record<string, unknown> | undefined;
|
||||
if (models) {
|
||||
for (const tier of ['default', 'fast', 'complex', 'local'] as const) {
|
||||
for (const tier of ['default', 'fast', 'complex', 'local']) {
|
||||
const m = models[tier] as Record<string, unknown> | undefined;
|
||||
if (m?.api_key) m.api_key = '***';
|
||||
if (m?.auth_token) m.auth_token = '***';
|
||||
redact(m, 'api_key', 'auth_token');
|
||||
const fb = m?.fallback as Record<string, unknown> | undefined;
|
||||
redact(fb, 'api_key', 'auth_token');
|
||||
}
|
||||
const localProviders = models.local_providers as Record<string, Record<string, unknown>> | undefined;
|
||||
if (localProviders) {
|
||||
for (const provider of Object.values(localProviders)) {
|
||||
if (provider.api_key) provider.api_key = '***';
|
||||
if (provider.auth_token) provider.auth_token = '***';
|
||||
redact(provider, 'api_key', 'auth_token');
|
||||
const fb = provider.fallback as Record<string, unknown> | undefined;
|
||||
redact(fb, 'api_key', 'auth_token');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Web search
|
||||
redact(raw.web_search as Record<string, unknown>, 'api_key');
|
||||
|
||||
// Audio
|
||||
redact(raw.audio as Record<string, unknown>, 'transcription_api_key');
|
||||
|
||||
// Memory → embedding
|
||||
const memory = raw.memory as Record<string, unknown> | undefined;
|
||||
if (memory) {
|
||||
redact(memory.embedding as Record<string, unknown>, 'api_key');
|
||||
}
|
||||
|
||||
// Automation — webhook HMAC secrets and gmail credential paths
|
||||
const automation = raw.automation as Record<string, unknown> | undefined;
|
||||
if (automation) {
|
||||
const webhooks = automation.webhooks as Record<string, unknown>[] | undefined;
|
||||
if (webhooks) {
|
||||
for (const wh of webhooks) {
|
||||
redact(wh, 'secret');
|
||||
}
|
||||
}
|
||||
const gmail = automation.gmail as Record<string, unknown> | undefined;
|
||||
redact(gmail, 'credentials_file', 'token_file');
|
||||
}
|
||||
|
||||
// MCP server env vars (may contain API keys or other secrets)
|
||||
const mcp = raw.mcp as Record<string, unknown> | undefined;
|
||||
if (mcp) {
|
||||
const servers = mcp.servers as Record<string, unknown>[] | undefined;
|
||||
if (servers) {
|
||||
for (const srv of servers) {
|
||||
if (srv.env && typeof srv.env === 'object') {
|
||||
const env = srv.env as Record<string, unknown>;
|
||||
for (const key of Object.keys(env)) {
|
||||
env[key] = '***';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user