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:
William Valentin
2026-02-09 10:32:57 -08:00
parent 1d126cddfb
commit 9be8f76bc7
26 changed files with 1395 additions and 105 deletions
+73 -13
View File
@@ -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] = '***';
}
}
}
}
}