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
+54 -4
View File
@@ -114,6 +114,12 @@ export function createClientFromConfig(cfg: ModelConfig): ModelClient {
apiKey: cfg.api_key ?? process.env.ZHIPUAI_API_KEY,
baseURL: cfg.endpoint ?? 'https://api.z.ai/api/paas/v4',
});
case 'xai':
return new OpenAIClient({
model: cfg.model,
apiKey: cfg.api_key ?? process.env.XAI_API_KEY,
baseURL: cfg.endpoint ?? 'https://api.x.ai/v1',
});
case 'bedrock':
return new BedrockClient({
model: cfg.model,
@@ -313,6 +319,8 @@ export function createModelRouter(config: Config): ModelRouter {
* Create the unified message handler for the channel registry.
* Each channel+sender pair gets its own AgentOrchestrator backed by a persistent session.
* The orchestrator wraps a NativeAgent and adds delegation to different model tiers.
*
* Returns both the message handler function and the agents map for usage tracking.
*/
function createMessageRouter(deps: {
sessionManager: SessionManager;
@@ -326,7 +334,10 @@ function createMessageRouter(deps: {
agentRouter?: AgentRouter;
sandboxManager?: SandboxManager;
audioConfig?: AudioTranscriptionConfig;
}) {
}): {
handler: (msg: InboundMessage, reply: (response: OutboundMessage) => Promise<void>) => Promise<void>;
agents: Map<string, { orchestrator: AgentOrchestrator; collector: OutboundAttachmentCollector }>;
} {
// Cache agents by session ID + agent config name to avoid recreating on every message
const agents = new Map<string, { orchestrator: AgentOrchestrator; collector: OutboundAttachmentCollector }>();
@@ -444,7 +455,7 @@ function createMessageRouter(deps: {
return entry;
}
return async (msg: InboundMessage, reply: (response: OutboundMessage) => Promise<void>): Promise<void> => {
const handler = async (msg: InboundMessage, reply: (response: OutboundMessage) => Promise<void>): Promise<void> => {
const { orchestrator: agent, collector } = getOrCreateAgent(msg.channel, msg.senderId);
// Handle special commands
@@ -524,6 +535,8 @@ function createMessageRouter(deps: {
});
}
};
return { handler, agents };
}
export async function startDaemon(config: Config): Promise<DaemonContext> {
@@ -780,6 +793,10 @@ export async function startDaemon(config: Config): Promise<DaemonContext> {
// Initialize channel registry (created early so the gateway can reference it)
const channelRegistry = new ChannelRegistry();
// Mutable reference to channel agents map — set after createMessageRouter() below.
// This allows the gateway's getTokenUsage callback to access channel agent usage data.
let channelAgents: Map<string, { orchestrator: AgentOrchestrator; collector: OutboundAttachmentCollector }> | null = null;
// Initialize gateway WebSocket server
const gateway = new GatewayServer({
port: config.server.port,
@@ -803,6 +820,35 @@ export async function startDaemon(config: Config): Promise<DaemonContext> {
// Exit with code 75 (EX_TEMPFAIL) — process supervisor should restart
process.exit(75);
},
getTokenUsage: () => {
const results: Array<{
sessionId: string;
primary: { inputTokens: number; outputTokens: number; calls: number };
delegation: Record<string, { inputTokens: number; outputTokens: number; calls: number }>;
total: { inputTokens: number; outputTokens: number; calls: number; estimatedCost: number };
}> = [];
// Collect usage from gateway WebSocket sessions (NativeAgent-based)
const sessionBridge = gateway.getSessionBridge();
for (const entry of sessionBridge.getAllUsage()) {
results.push(entry);
}
// Collect usage from channel agents (AgentOrchestrator-based, has full delegation data)
if (channelAgents) {
for (const [sessionId, { orchestrator }] of channelAgents) {
const usage = orchestrator.getUsage();
results.push({
sessionId,
primary: usage.primary,
delegation: usage.delegation,
total: usage.total,
});
}
}
return results;
},
});
if (config.server.token) {
@@ -812,7 +858,7 @@ export async function startDaemon(config: Config): Promise<DaemonContext> {
// ── Channel Registry ──────────────────────────────────────────
// Set up the unified message handler
channelRegistry.setMessageHandler(createMessageRouter({
const messageRouter = createMessageRouter({
sessionManager,
modelRouter,
systemPrompt,
@@ -824,7 +870,11 @@ export async function startDaemon(config: Config): Promise<DaemonContext> {
agentRouter,
sandboxManager,
audioConfig,
}));
});
channelRegistry.setMessageHandler(messageRouter.handler);
// Wire channel agents into the getTokenUsage callback (late binding)
channelAgents = messageRouter.agents;
// Register Telegram adapter
const telegramAdapter = new TelegramAdapter({