Skip to content

feat(machine-integration): Implement Milestone 1 - Foundation layer#21

Merged
0xtsotsi merged 4 commits intomainfrom
vk/1e60-machine-integrat
Dec 27, 2025
Merged

feat(machine-integration): Implement Milestone 1 - Foundation layer#21
0xtsotsi merged 4 commits intomainfrom
vk/1e60-machine-integrat

Conversation

@0xtsotsi
Copy link
Owner

@0xtsotsi 0xtsotsi commented Dec 27, 2025

Machine Integration - Milestone 1: Foundation

This PR implements the foundational infrastructure for multi-engine AI provider support with unified telemetry, capability probing, and agent monitoring.

Summary

Implements Milestone 1: Foundation (12 tasks) from the Machine Integration specification:

  • Provider Registry with auto-discovery and fallback chains
  • Runtime capability probing with caching
  • Enhanced Claude provider with telemetry
  • New Cursor provider implementation
  • Authentication caching layer
  • SQLite-based agent monitoring with PID tracking
  • Unified telemetry interface across providers
  • Provider management API routes

Changes

Provider Registry (apps/server/src/providers/registry.ts)

  • Singleton EngineRegistry with Map-based O(1) provider lookup
  • Auto-discovery via onRegister() lifecycle hooks
  • Fallback chain management for provider failover
  • Per-provider metadata tracking with priority-based routing

Capability Probing (apps/server/src/providers/capability-probe.ts)

  • Runtime detection of provider capabilities (vision, tools, streaming, etc.)
  • Authentication method detection (api-key, oauth, cli-auth)
  • Rate limit probing with 5-minute TTL caching
  • ProviderCapability and ProviderLimits interfaces

Claude Provider Enhancement (apps/server/src/providers/claude-provider.ts)

  • isAuthenticated() method for auth status checking
  • getCapabilities() returning provider capability summary
  • Integrated telemetryParser for usage parsing

Cursor Provider (apps/server/src/providers/cursor-provider.ts)

  • New CursorProvider extending BaseProvider
  • CLI-based query execution
  • Authentication detection via cursor --version
  • Model listing for Cursor-hosted Claude models

Auth Cache (apps/server/src/providers/auth-cache.ts)

  • AuthCache class with Map-based O(1) lookups
  • Configurable TTL (default 5 minutes)
  • Cache statistics (hits, misses, hit rate)

Agent Monitor Service (apps/server/src/services/agent-monitor.ts)

  • SQLite-based agent execution tracking with PID monitoring
  • Parent-child relationship tracking for sub-agents
  • Automatic dead process detection and cleanup
  • Telemetry aggregation per agent

Unified Telemetry (apps/server/src/lib/telemetry*.ts)

  • ParsedTelemetry interface across all providers
  • Model-specific pricing (Opus: $15/M in, $75/M out)
  • Claude SDK and Cursor CLI telemetry parsers
  • TelemetryCapture factory with parser routing

Provider API Routes (apps/server/src/routes/providers/)

  • GET /api/providers/list - List all registered providers
  • POST /api/providers/test/:id - Test provider auth
  • POST /api/providers/probe/:id - Probe capabilities
  • GET /api/providers/status - Active provider + fallbacks

CI/CD (.github/workflows/provider-check.yml)

  • Provider infrastructure validation workflow
  • better-sqlite3 compatibility testing
  • Provider export and telemetry parser validation

Test Plan

  • Verify provider registry loads all providers correctly
  • Test capability probing for Claude provider
  • Test authentication caching behavior
  • Verify agent monitor service creates SQLite schema
  • Test provider API endpoints manually
  • Run npm run build:packages successfully
  • Run CI workflow on PR

Breaking Changes

None - this is new functionality.

Related Issues

Covers tasks M1-T1 through M1-T12 from the Machine Integration specification.

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • New Features

    • Added provider management system with capability probing, authentication status tracking, and rate limit configuration
    • Introduced Cursor IDE integration for AI-assisted queries with model support
    • Implemented telemetry tracking system to monitor token usage and compute costs
    • Added agent execution monitoring with persistent storage and PID tracking
    • New provider API endpoints for listing providers, checking status, and testing authentication
  • Chores

    • Added GitHub Actions workflow for provider validation and integration testing
    • Enhanced provider infrastructure with registry, caching, and diagnostic tooling

✏️ Tip: You can customize this high-level summary in your review settings.

0xtsotsi and others added 2 commits December 26, 2025 07:06
- Fix property name mismatches in hook parameters (_currentProject, _loadIssues)
- Update drag event type compatibility for @dnd-kit/core
- Add proper DragStartEvent and DragEndEvent type imports
- Add validation, rate limiting, and JSON parsing middleware
- Add unit tests for beads service and utilities

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Add comprehensive provider infrastructure enabling multi-engine AI
support with unified telemetry, capability probing, and agent monitoring.

**Provider Registry (M1-T1)**
- Singleton EngineRegistry with Map-based O(1) provider lookup
- Auto-discovery via onRegister() lifecycle hooks
- Fallback chain management for provider failover
- Per-provider metadata tracking with priority-based routing

**Capability Probing (M1-T2)**
- Runtime detection of provider capabilities (vision, tools, streaming)
- Authentication method detection (api-key, oauth, cli-auth)
- Rate limit probing with 5-minute TTL caching
- ProviderCapability and ProviderLimits interfaces

**Claude Provider Enhancement (M1-T3)**
- Add isAuthenticated() method for auth status checking
- Add getCapabilities() returning provider capability summary
- Integrate telemetryParser for usage parsing
- Export claude-telemetry parser for SDK output

**Cursor Provider (M1-T4)**
- New CursorProvider extending BaseProvider
- CLI-based query execution with streaming support
- Authentication detection via cursor --version
- Model listing for Cursor-hosted Claude models

**Auth Cache (M1-T5)**
- AuthCache class with Map-based O(1) lookups
- Configurable TTL (default 5 minutes)
- Cache statistics (hits, misses, hit rate)
- getOrCompute() for lazy auth checking

**Agent Monitor Service (M1-T6, M1-T7)**
- SQLite-based agent execution tracking with PID monitoring
- Parent-child relationship tracking for sub-agents
- Automatic dead process detection and cleanup
- Telemetry aggregation per agent
- Agent tree queries and statistics

**Unified Telemetry (M1-T8, M1-T9, M1-T10, M1-T11)**
- ParsedTelemetry interface across all providers
- Model-specific pricing (Opus: $15/M in, $75/M out)
- Claude SDK telemetry parser
- Cursor CLI telemetry parser
- TelemetryCapture factory with parser routing

**Provider API Routes (M1-T12)**
- GET /api/providers/list - List all registered providers
- POST /api/providers/test/:id - Test provider auth
- POST /api/providers/probe/:id - Probe capabilities
- GET /api/providers/status - Active provider + fallbacks

**CI/CD**
- Add provider-check.yml workflow for provider infrastructure validation
- Test better-sqlite3 compatibility for agent monitor
- Validate provider exports and telemetry parsers

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@coderabbitai
Copy link

coderabbitai bot commented Dec 27, 2025

Caution

Review failed

The pull request is closed.

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

📝 Walkthrough

Walkthrough

This PR introduces a comprehensive multi-provider infrastructure system, including a provider registry with capability probing, TTL-based authentication caching, and provider-specific telemetry parsers for Claude and Cursor. It adds SQLite-backed agent monitoring with PID tracking and telemetry aggregation, along with new API routes for provider management. Minor UI restructuring updates logo formatting.

Changes

Cohort / File(s) Summary
Provider Infrastructure Foundation
apps/server/src/providers/base-provider.ts
Enhanced BaseProvider with new public methods: getAuthenticationMethods(), getRateLimits(), and optional getCapabilities(). Added AuthMethod type and RateLimit interface for capability declaration.
Telemetry Core & Parsers
apps/server/src/lib/telemetry.ts, apps/server/src/lib/telemetry-factory.ts, apps/server/src/providers/claude-telemetry.ts, apps/server/src/providers/cursor-telemetry.ts
Established unified telemetry system with ParsedTelemetry, TelemetryParser types, cost calculation, and provider-agnostic capture creation. Factory module routes telemetry to provider-specific parsers. Claude and Cursor parsers extract tokens, cost, duration from JSON-lines and text formats with model-aware pricing.
Provider Implementations
apps/server/src/providers/claude-provider.ts, apps/server/src/providers/cursor-provider.ts
ClaudeProvider augmented with telemetryParser, isAuthenticated(), getCapabilities(), and getRateLimits(). CursorProvider newly introduced as CLI-based provider with Cursor IDE integration, async query execution, and capability/model discovery.
Provider Registry & Discovery
apps/server/src/providers/registry.ts, apps/server/src/providers/auth-cache.ts, apps/server/src/providers/capability-probe.ts
Singleton EngineRegistry manages provider lifecycle, models, and fallback chains. AuthCache provides TTL-based in-memory authentication status caching with hit/miss tracking. ProviderCapabilityProbe queries and caches provider capabilities (auth, vision, tools, streaming, limits) with configurable TTL.
Provider Exports
apps/server/src/providers/index.ts
Barrel module exporting all provider entities: implementations (BaseProvider, ClaudeProvider, CursorProvider), registry and helpers, capability probe, auth cache, telemetry parsers, and shared types for consolidated imports.
Provider Routes
apps/server/src/routes/providers/common.ts, apps/server/src/routes/providers/index.ts, apps/server/src/routes/providers/routes/list.ts, apps/server/src/routes/providers/routes/status.ts, apps/server/src/routes/providers/routes/test.ts, apps/server/src/routes/providers/routes/probe.ts
New /api/providers/* REST API: list providers with optional capabilities, get status and fallback chain, test provider authentication via authCache, probe single or all providers for capabilities with caching. All handlers include error logging and standardized responses.
Agent Monitoring Service
apps/server/src/services/agent-monitor.ts
SQLite-backed AgentMonitorService tracks AI agent executions with hierarchical relationships, status lifecycle, PID monitoring, telemetry aggregation, and orphaned process cleanup. Exposes factory and initializer for singleton management across data directories.
CI/CD Validation
.github/workflows/provider-check.yml
New GitHub Actions workflow validates provider exports (EngineRegistry, ClaudeProvider, CursorProvider), telemetry exports, SQLite compatibility, agent-monitor exports, server tests, and provider registry integration tests on PR and push to main/master.
Settings & UI Updates
apps/server/src/routes/settings/routes/update-auth-method.ts, apps/ui/src/components/layout/sidebar/components/devflow-logo.tsx, apps/ui/src/components/splash-screen.tsx, apps/ui/src/components/views/context-view.tsx
Minor refactoring: consolidated validation logic in auth-method handler; restructured logo markup with line breaks for "brnd" styling; collapsed message definition in context view. No behavioral changes.

Sequence Diagrams

sequenceDiagram
    participant Client
    participant API as /api/providers
    participant Registry as EngineRegistry
    participant Probe as CapabilityProbe
    participant Provider as BaseProvider
    participant Cache as Cache (TTL)

    Client->>API: POST /providers/probe/{providerId}
    API->>Registry: get(providerId)
    Registry-->>API: ProviderMetadata
    API->>Probe: probeWithResult(providerId, provider)
    
    rect rgb(200, 230, 255)
        Note over Probe,Cache: Check cache for existing probe result
        Probe->>Cache: lookup(providerId)
        alt Cache hit and not expired
            Cache-->>Probe: ProviderCapability (cached)
        else Cache miss or expired
            Probe->>Provider: detectAuth()
            Provider-->>Probe: AuthMethod[]
            Probe->>Provider: testCapability(capability)
            Provider-->>Probe: boolean
            Probe->>Provider: probeLimits()
            Provider-->>Probe: RateLimit
            Probe->>Cache: store(providerId, capability, timestamp)
        end
    end
    
    Probe-->>API: ProbeResult {capability, limits, cacheHit, timestamp}
    API-->>Client: JSON response with capabilities
Loading
sequenceDiagram
    participant Agent as AI Agent
    participant Monitor as AgentMonitorService
    participant DB as SQLite DB
    participant TelemetryFactory as TelemetryFactory
    participant Parser as ProviderParser

    Agent->>Monitor: register(agentRecord)
    Monitor->>DB: INSERT agent
    DB-->>Monitor: agentId

    Agent->>Monitor: start(id, pid)
    Monitor->>DB: UPDATE status=running, pid
    Monitor->>Monitor: startPIDMonitoring()

    Agent->>Agent: execute query
    Agent->>TelemetryFactory: createTelemetryCapture(engine, model, output)
    TelemetryFactory->>Parser: parse(output)
    Parser-->>TelemetryFactory: ParsedTelemetry
    TelemetryFactory-->>Agent: CapturedTelemetry

    Agent->>Monitor: complete(id, telemetry)
    Monitor->>DB: UPDATE status=completed, telemetry JSON
    
    rect rgb(230, 200, 255)
        Note over Monitor,DB: Periodic PID monitoring
        Monitor->>Monitor: checkDeadProcesses()
        Monitor->>Monitor: isProcessAlive(pid)
        alt Process dead
            Monitor->>Monitor: cleanupOrphanedPIDs()
            Monitor->>DB: UPDATE orphaned agents
        end
    end

    Agent->>Monitor: getStats()
    Monitor->>DB: SELECT aggregated data
    DB-->>Monitor: stats (tokens, cost, duration by status/engine)
    Monitor-->>Agent: AgentStats
Loading

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly Related PRs

Poem

🐰 A registry of providers, telemetry refined,
With caching clever and capabilities probed,
Agents now tracked through SQLite's embrace—
Multi-provider dreams in one unified space!

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title clearly and specifically summarizes the main change: implementing Milestone 1 of a machine integration feature with foundational infrastructure.
Docstring Coverage ✅ Passed Docstring coverage is 88.64% which is sufficient. The required threshold is 80.00%.

📜 Recent review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d6e1e6e and 64dbd82.

📒 Files selected for processing (5)
  • apps/server/src/providers/claude-provider.ts
  • apps/server/src/routes/settings/routes/update-auth-method.ts
  • apps/ui/src/components/layout/sidebar/components/devflow-logo.tsx
  • apps/ui/src/components/splash-screen.tsx
  • apps/ui/src/components/views/context-view.tsx

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist
Copy link

Summary of Changes

Hello @0xtsotsi, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request lays the groundwork for a flexible and scalable multi-AI provider system. It establishes core components for managing different AI engines, dynamically assessing their features, efficiently handling authentication, and comprehensively monitoring agent activities. This foundational layer is critical for future expansions, ensuring consistent operational insights and seamless integration of diverse AI capabilities.

Highlights

  • Multi-Engine AI Provider Foundation: Introduces the foundational infrastructure for supporting multiple AI providers, including a dynamic registry, capability probing, and unified telemetry.
  • Provider Registry: Implements an EngineRegistry with auto-discovery, O(1) lookup, fallback chain management, and per-provider metadata tracking.
  • Runtime Capability Probing: Adds a ProviderCapabilityProbe for runtime detection of provider capabilities (e.g., vision, tools, streaming) and authentication methods, with 5-minute TTL caching.
  • Enhanced Claude Provider: The existing Claude provider is enhanced with isAuthenticated() and getCapabilities() methods, and integrates a new claudeTelemetryParser.
  • New Cursor Provider: A new CursorProvider is implemented, extending BaseProvider to execute queries via the Cursor CLI, detect authentication, and list Cursor-hosted Claude models.
  • Authentication Caching: A new AuthCache class provides a configurable TTL cache for authentication statuses, reducing redundant checks against providers.
  • SQLite-based Agent Monitoring: A robust AgentMonitorService is introduced to track AI agent executions using a SQLite database, featuring PID monitoring, parent-child tracking, process death detection, and telemetry aggregation.
  • Unified Telemetry Interface: Defines a ParsedTelemetry interface and TelemetryCaptureFactory to standardize telemetry data across all providers, including token usage, cost calculation, and duration tracking.
  • Provider Management API Routes: New API routes are added under /api/providers/ for listing registered providers, testing authentication, probing capabilities, and checking provider status and fallbacks.
Ignored Files
  • Ignored by pattern: .github/workflows/** (1)
    • .github/workflows/provider-check.yml
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This PR introduces a significant amount of foundational code for multi-provider support, including telemetry, capability probing, and agent monitoring. The overall structure is well-thought-out, but there are several critical areas that need attention regarding correctness, performance, and design before this can be considered production-ready. Key issues include potential race conditions in telemetry aggregation, inefficient database queries, hardcoded provider-specific logic in generic components, and incomplete or fragile implementations in areas like CLI streaming and authentication checks. Addressing these points will greatly improve the robustness and extensibility of this new foundation.


// Check authentication by trying a simple command
// (Cursor may not have an explicit auth check command)
const authenticated = true; // Assume authenticated if CLI is available

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The authentication check for the Cursor provider assumes it's authenticated if the CLI is available (const authenticated = true;). This is a fragile assumption that could lead to runtime errors if the CLI is installed but not logged in. A more robust check should be implemented, for example by running a command that requires authentication and checking its output.

Comment on lines 346 to 372
addTelemetry(id: string, telemetry: ParsedTelemetry): boolean {
const existing = this.getAgent(id);

if (!existing) {
return false;
}

// Aggregate with existing telemetry
const aggregated = existing.telemetry
? {
tokensIn: existing.telemetry.tokensIn + telemetry.tokensIn,
tokensOut: existing.telemetry.tokensOut + telemetry.tokensOut,
cached: existing.telemetry.cached + telemetry.cached,
cost: existing.telemetry.cost + telemetry.cost,
duration: existing.telemetry.duration + telemetry.duration,
}
: telemetry;

const stmt = this.db.prepare(`
UPDATE agents
SET telemetry_json = ?
WHERE id = ?
`);

const result = stmt.run(JSON.stringify(aggregated), id);
return result.changes > 0;
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The addTelemetry method performs a read-modify-write operation that is not atomic. If this method is called concurrently for the same agent ID, it could lead to a race condition where telemetry updates are lost. To ensure data integrity, this operation should be made atomic, for example by using a database transaction or a more advanced SQL update statement that performs the aggregation on the database side.

Comment on lines 229 to 231
export function generateTelemetryId(): string {
return `tel_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`;
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The current implementation of generateTelemetryId using Date.now() and Math.random() does not guarantee uniqueness, especially under high load, which could lead to data collision and corruption in your telemetry system. It's highly recommended to use a more robust method for generating unique IDs, such as crypto.randomUUID() which is available in Node.js. You'll also need to add import { randomUUID } from 'crypto'; at the top of the file.

Suggested change
export function generateTelemetryId(): string {
return `tel_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`;
}
export function generateTelemetryId(): string {
return `tel_${randomUUID()}`;
}

Comment on lines 144 to 167
async detectAuth(provider: BaseProvider): Promise<AuthMethod[]> {
const methods: AuthMethod[] = [];
const name = provider.getName();

// Check for API key authentication
if (name === 'claude' && process.env.ANTHROPIC_API_KEY) {
methods.push('api-key');
}

// Check for CLI-based authentication
const installStatus = await provider.detectInstallation();
if (installStatus.authenticated) {
if (installStatus.method === 'cli') {
methods.push('cli-auth');
}
}

// Default to 'none' if no auth found but provider is installed
if (methods.length === 0 && installStatus.installed) {
methods.push('none');
}

return methods;
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The detectAuth method contains hardcoded logic specific to claude and cursor providers. This violates the separation of concerns and makes the capability probe difficult to extend. This logic should be moved into the respective provider classes. Consider adding a method like getAuthenticationMethods(): Promise<AuthMethod[]> to the BaseProvider class and calling it here.

Comment on lines 190 to 208
async probeLimits(provider: BaseProvider): Promise<ProviderCapability['rateLimit']> {
const name = provider.getName();

// Known rate limits for popular providers
switch (name) {
case 'claude':
return {
requestsPerMinute: 50, // Default for most tiers
concurrent: 5,
};
case 'cursor':
return {
requestsPerMinute: 60,
concurrent: 3,
};
default:
return undefined;
}
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Similar to detectAuth, the probeLimits method has hardcoded rate limit information for providers. This logic should be encapsulated within each provider. I suggest adding a getRateLimits(): Promise<ProviderCapability['rateLimit']> method to the BaseProvider class to make this component more extensible.

Comment on lines 42 to 45
if (
'getCapabilities' in metadata.provider &&
typeof metadata.provider.getCapabilities === 'function'
) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Checking for the existence of getCapabilities using the in operator is a form of duck typing that can be brittle. For better type safety and cleaner code, consider adding an optional getCapabilities method to the BaseProvider abstract class. This would make the check a simple if (metadata.provider.getCapabilities).

Comment on lines +91 to +93
console.warn(
`[TelemetryFactory] No parser found for provider "${engine}", returning empty telemetry`
);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using console.warn for logging in a server application is not ideal as it lacks structure and can't be easily configured for different environments. Consider using a dedicated logger instance for better observability and control over log levels.

Comment on lines 46 to 70
getCapabilities(): {
supportsPlanning: boolean;
supportsVision: boolean;
supportsTools: boolean;
supportsStreaming: boolean;
supportsSystemPrompt: boolean;
supportsConversationHistory: boolean;
supportsMCP: boolean;
supportsThinking: boolean;
maxContextWindow: number;
maxOutputTokens: number;
} {
return {
supportsPlanning: true,
supportsVision: true,
supportsTools: true,
supportsStreaming: true,
supportsSystemPrompt: true,
supportsConversationHistory: true,
supportsMCP: true,
supportsThinking: true,
maxContextWindow: 200000,
maxOutputTokens: 16000,
};
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The getCapabilities method returns hardcoded values for maxContextWindow and maxOutputTokens. These values are already defined in the models returned by getAvailableModels(). To avoid duplication and ensure consistency, these capability values should be derived dynamically from the available models. This makes the provider more maintainable as you only need to update model definitions in one place.

  getCapabilities() {
    const models = this.getAvailableModels();
    const maxContextWindow = Math.max(...models.map((m) => m.contextWindow ?? 0));
    const maxOutputTokens = Math.max(...models.map((m) => m.maxOutputTokens ?? 0));

    return {
      supportsPlanning: true,
      supportsVision: true,
      supportsTools: true,
      supportsStreaming: true,
      supportsSystemPrompt: true,
      supportsConversationHistory: true,
      supportsMCP: true,
      supportsThinking: true,
      maxContextWindow,
      maxOutputTokens,
    };
  }

Comment on lines +130 to +131
// Ambiguous, assume input
tokensIn = parseInt(match[1], 10);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The logic for parsing text-based telemetry is ambiguous. When a single token count is found without an 'in' or 'out' keyword, it's assumed to be input tokens. This could lead to incorrect telemetry data. It would be safer to log a warning about the ambiguous format or require a more explicit output format from the CLI.

Comment on lines +783 to +788
export function getAgentMonitor(dataDir: string): AgentMonitorService {
if (!agentMonitorInstance || agentMonitorInstance['dbPath'] !== path.join(dataDir, 'agents.db')) {
agentMonitorInstance = new AgentMonitorService(dataDir);
}
return agentMonitorInstance;
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The singleton factory getAgentMonitor accesses the private property dbPath of the AgentMonitorService instance using bracket notation (agentMonitorInstance['dbPath']). This breaks encapsulation and is a code smell. A cleaner approach would be to manage the singleton instance without relying on its internal state, or to expose a public method for this check if necessary.

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

export const createBeadsIssueSchema = z.object({
title: z
.string()
.min(1, 'Title is required')
.max(200, 'Title must be 200 characters or less')

P2 Badge Preserve parentIssueId in create issue schema

The create-issue route validates with createBeadsIssueSchema, which no longer includes parentIssueId. Zod strips unknown fields, so parentIssueId supplied by clients is discarded and beads-service never adds --parent, breaking subtask creation via the API. Add parentIssueId back to the schema or explicitly allow it in the route.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +69 to +73
export function createTelemetryCapture(
engine: string,
model: string,
output: string,
workingDir: string,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Rename telemetry factory export to avoid import collision

This file imports createTelemetryCapture from telemetry.js and then later declares another export function createTelemetryCapture(...) with the same identifier. In ESM/TypeScript that’s a duplicate binding and the module won’t compile (the provider-check workflow runs a server build). Rename the local factory or alias the import to avoid the redeclaration.

Useful? React with 👍 / 👎.

Comment on lines 285 to 288
private runCursorCommand(args: string[]): Promise<string> {
return new Promise((resolve, reject) => {
const child = spawn(this.cliPath, args);
let output = '';

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Pass cwd to Cursor CLI spawn

The Cursor provider receives cwd in executeQuery, but runCursorCommand spawns the CLI without a working directory. Requests that expect project-specific context will run from the server’s cwd instead, so Cursor may read the wrong workspace or fail to access project files. Thread cwd into spawn (or pass it into runCursorCommand) to ensure correct context.

Useful? React with 👍 / 👎.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 12

🧹 Nitpick comments (19)
apps/server/src/providers/auth-cache.ts (2)

137-142: Inconsistency between clear() and invalidateAll() behavior.

invalidateAll() clears entries but preserves hit/miss stats, while clear() resets both. This inconsistency could confuse callers. Consider documenting this difference or aligning the behavior.

🔎 Option: Document the behavioral difference
   /**
    * Invalidate all cache entries
+   *
+   * Note: This preserves hit/miss statistics. Use clear() to also reset stats.
    */
   invalidateAll(): void {
     this.cache.clear();
   }

114-125: Consider whether to cache failures in getOrCompute.

Currently, if fn() throws, the error propagates without caching. This is likely intentional to allow retries on transient failures, but you may want to document this behavior or optionally cache negative results for a shorter TTL to prevent thundering herd on repeated failures.

apps/server/src/providers/capability-probe.ts (3)

370-378: getCacheStats() returns placeholder values instead of actual statistics.

The method always returns { hits: 0, misses: 0, size: ... }. The AuthCache class in the same codebase tracks these metrics properly. Consider implementing actual hit/miss tracking for consistency and observability.

🔎 Proposed implementation with hit/miss tracking

Add private counters to the class:

 export class ProviderCapabilityProbe {
   private cache = new Map<string, CachedCapability>();
   private cacheTTL: number;
+  private hits = 0;
+  private misses = 0;

Update the probe method to track cache hits/misses:

     if (cached && now - cached.timestamp < this.cacheTTL) {
       console.log(`[CapabilityProbe] Cache hit for provider "${providerId}"`);
+      this.hits++;
       return cached.capability;
     }
+    this.misses++;

Update getCacheStats:

   getCacheStats(): { hits: number; misses: number; size: number } {
-    // For simplicity, we're not tracking hits/misses in this implementation
-    // A production version would add counters for better observability
     return {
-      hits: 0,
-      misses: 0,
+      hits: this.hits,
+      misses: this.misses,
       size: this.cache.size,
     };
   }

144-167: Auth detection is tightly coupled to specific provider names.

The detectAuth method hardcodes the "claude" provider check for ANTHROPIC_API_KEY. Consider abstracting this to make the probe more extensible as new providers are added.


262-266: Consider using structured logging instead of console.log.

The provider routes use createLogger from @automaker/utils for consistent logging. Using console.log here creates inconsistency in log format and makes it harder to filter/aggregate logs.

apps/server/src/providers/cursor-telemetry.ts (1)

102-142: Pattern matching logic may not behave as expected.

The text format parser iterates through all patterns, but the logic for determining input vs. output tokens based on the pattern source string is fragile. The pattern.source.includes('in') check on line 125 will match patterns containing 'in' anywhere (e.g., 'input' contains 'in'), but patterns like tokens?\s*(?:out|output)?:?\s*(\d+)/i at line 109 also contain 'in' due to the negative pattern. Additionally, both tokenPatterns and outputPatterns may match the same text.

Consider restructuring to associate each pattern with its token type explicitly:

Suggested approach
const patterns = [
  { regex: /cursor\s+request[:\s]*(\d+)\s*in,\s*(\d+)\s*out/i, type: 'both' },
  { regex: /tokens?\s*(?:in|input):?\s*(\d+)/i, type: 'in' },
  { regex: /tokens?\s*(?:out|output):?\s*(\d+)/i, type: 'out' },
  // ...
] as const;
apps/server/src/providers/claude-telemetry.ts (2)

166-189: Duration parsing heuristic may misclassify values.

Line 180 uses value > 100 to distinguish milliseconds from seconds, but this heuristic fails for durations like 150 seconds (2.5 minutes) which would be incorrectly treated as milliseconds. Consider making the pattern matching more explicit about units.

Suggested improvement
 function extractDuration(output: string): number {
   const durationPatterns = [
-    /duration[:\s]+(\d+(?:\.\d+)?)\s*s/i,
-    /took[:\s]+(\d+(?:\.\d+)?)\s*s/i,
-    /(\d+(?:\.\d+)?)\s*seconds?/i,
-    /(\d+)\s*ms/i,
+    { regex: /duration[:\s]+(\d+(?:\.\d+)?)\s*s(?:ec|econds?)?/i, unit: 's' },
+    { regex: /took[:\s]+(\d+(?:\.\d+)?)\s*s(?:ec|econds?)?/i, unit: 's' },
+    { regex: /(\d+(?:\.\d+)?)\s*seconds?/i, unit: 's' },
+    { regex: /(\d+)\s*ms/i, unit: 'ms' },
+    { regex: /(\d+)\s*milliseconds?/i, unit: 'ms' },
   ];

-  for (const pattern of durationPatterns) {
-    const match = output.match(pattern);
+  for (const { regex, unit } of durationPatterns) {
+    const match = output.match(regex);
     if (match) {
       const value = parseFloat(match[1]);
-      if (pattern.source.includes('ms') || value > 100) {
+      if (unit === 'ms') {
         return Math.round(value);
       }
       return Math.round(value * 1000);
     }
   }

   return 0;
 }

244-248: Parser recreation on each call may be inefficient.

parseClaudeTelemetry creates a new parser instance on every call via createClaudeTelemetryParser(model). For high-frequency telemetry parsing, consider caching parsers by model.

apps/server/src/lib/telemetry-factory.ts (1)

30-34: Module-level mutable state may cause issues in testing and concurrent usage.

PROVIDER_PARSERS is a mutable object at module scope. While clearTelemetryParsers() exists for testing, concurrent test runs or dynamic registration could lead to race conditions.

Consider documenting this limitation or using a more robust pattern for provider parser management if concurrent access is expected.

apps/server/src/lib/telemetry.ts (2)

255-274: Duration aggregation may not be semantically meaningful.

aggregateTelemetry sums durations across captures (line 270), but if captures represent sequential operations, the total duration is the sum. If they represent parallel operations, the total should be the maximum. Consider clarifying the expected use case or documenting this behavior.


95-120: Consider extracting pricing from configuration rather than hardcoding.

The prices are currently accurate (as of December 2025), but hardcoding model-specific rates means they'll need code updates whenever Anthropic adjusts pricing. Loading from configuration would reduce maintenance burden and allow pricing updates without code changes.

apps/server/src/providers/cursor-provider.ts (2)

187-214: Authentication is assumed without actual verification.

Line 197 sets authenticated = true unconditionally when the CLI is available. The comment acknowledges this limitation, but it may lead to runtime failures if the user isn't actually authenticated with Cursor.

Consider attempting a lightweight authenticated operation to verify, or document this limitation more prominently.


285-311: CLI spawn lacks working directory and timeout handling.

The spawn call on line 287 doesn't specify a cwd option, so it inherits the process's current directory. Additionally, there's no timeout—a hanging CLI process will block indefinitely.

Consider adding:

  • A cwd option from ExecuteOptions
  • An AbortController or timeout mechanism
Suggested improvements
-  private runCursorCommand(args: string[]): Promise<string> {
+  private runCursorCommand(args: string[], options?: { cwd?: string; timeoutMs?: number }): Promise<string> {
     return new Promise((resolve, reject) => {
-      const child = spawn(this.cliPath, args);
+      const child = spawn(this.cliPath, args, { cwd: options?.cwd });
       let output = '';
       let errorOutput = '';
+
+      const timeout = options?.timeoutMs ? setTimeout(() => {
+        child.kill();
+        reject(new Error('Cursor CLI command timed out'));
+      }, options.timeoutMs) : undefined;

       // ... existing handlers ...

       child.on('close', (code) => {
+        if (timeout) clearTimeout(timeout);
         if (code === 0) {
           resolve(output);
         } else {
           reject(new Error(`Cursor CLI command failed: ${errorOutput || output}`));
         }
       });
apps/server/src/providers/registry.ts (3)

96-123: Async registration callbacks are fire-and-forget.

this.onRegister(metadata) at line 120 calls an async method without await. Registration callbacks (which may be Promise-returning) will execute in the background, and any errors or completion won't be reflected in the register() return flow.

If callback completion is important before returning metadata, consider awaiting it. If fire-and-forget is intentional, add .catch() to avoid unhandled rejections bubbling up.

🔎 Option A: Await the callbacks
-  register(id: string, provider: BaseProvider, config: ProviderConfig = {}): ProviderMetadata {
+  async register(id: string, provider: BaseProvider, config: ProviderConfig = {}): Promise<ProviderMetadata> {
     // ... existing code ...
 
-    this.onRegister(metadata);
+    await this.onRegister(metadata);
 
     return metadata;
   }
🔎 Option B: Explicitly handle rejections if fire-and-forget is intentional
-    this.onRegister(metadata);
+    this.onRegister(metadata).catch((err) => {
+      console.error(`[EngineRegistry] Unhandled error in registration callbacks for "${id}":`, err);
+    });

256-265: update() allows overwriting identity fields.

Object.assign(metadata, updates) permits callers to overwrite critical fields like id or provider, which could break registry invariants (e.g., Map key mismatch with id).

🔎 Proposed fix: Exclude identity fields from updates
   update(id: string, updates: Partial<ProviderMetadata>): ProviderMetadata | undefined {
     const metadata = this.providers.get(id);
 
     if (!metadata) {
       return undefined;
     }
 
-    Object.assign(metadata, updates);
+    const { id: _id, provider: _provider, ...safeUpdates } = updates;
+    Object.assign(metadata, safeUpdates);
     return metadata;
   }

141-154: Model lookup is O(n×m); consider indexing if model count grows.

Both getProviderForModel() and getMetadataForModel() iterate over all providers and their models. For a small number of providers/models this is fine, but if usage scales, consider building a model→provider index on registration.

Also applies to: 162-174

apps/server/src/services/agent-monitor.ts (3)

783-788: Accessing private property via bracket notation is a code smell.

agentMonitorInstance['dbPath'] bypasses TypeScript's private access check. Consider exposing a getter or using a different comparison approach.

🔎 Proposed fix: Add a public getter for path comparison

In the class:

+  getDbPath(): string {
+    return this.dbPath;
+  }

Then in getAgentMonitor:

-  if (!agentMonitorInstance || agentMonitorInstance['dbPath'] !== path.join(dataDir, 'agents.db')) {
+  if (!agentMonitorInstance || agentMonitorInstance.getDbPath() !== path.join(dataDir, 'agents.db')) {
     agentMonitorInstance = new AgentMonitorService(dataDir);
   }

475-485: Unbounded recursion in buildTreeNode could cause stack overflow.

If the agent hierarchy has cycles (due to data corruption) or extreme depth, buildTreeNode will overflow the stack. Consider adding a depth limit.

🔎 Proposed fix: Add maximum depth guard
-  private buildTreeNode(agent: AgentRecord, depth: number): AgentTreeNode {
+  private static readonly MAX_TREE_DEPTH = 100;
+
+  private buildTreeNode(agent: AgentRecord, depth: number): AgentTreeNode {
+    if (depth >= AgentMonitorService.MAX_TREE_DEPTH) {
+      console.warn(`[AgentMonitor] Max tree depth reached for agent ${agent.id}`);
+      return { ...agent, children: [], depth };
+    }
+
     const children = this.getAgents({ parentId: agent.id }).map((child) =>
       this.buildTreeNode(child, depth + 1)
     );

683-685: getActiveAgents() makes two separate queries; consider combining.

Two separate getAgents() calls with spreading could be combined into a single SQL query for efficiency.

🔎 Proposed fix: Single query with OR condition
   getActiveAgents(): AgentRecord[] {
-    return [...this.getAgents({ status: 'running' }), ...this.getAgents({ status: 'starting' })];
+    const stmt = this.db.prepare(`
+      SELECT * FROM agents WHERE status IN ('running', 'starting') ORDER BY created_at DESC
+    `);
+    const rows = stmt.all() as unknown[];
+    return rows.map((r) => this.rowToAgent(r)).filter((r): r is AgentRecord => r !== null);
   }
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7799c80 and ed1f76a.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (19)
  • .github/workflows/provider-check.yml
  • apps/server/src/lib/beads-validation.ts
  • apps/server/src/lib/telemetry-factory.ts
  • apps/server/src/lib/telemetry.ts
  • apps/server/src/providers/auth-cache.ts
  • apps/server/src/providers/capability-probe.ts
  • apps/server/src/providers/claude-provider.ts
  • apps/server/src/providers/claude-telemetry.ts
  • apps/server/src/providers/cursor-provider.ts
  • apps/server/src/providers/cursor-telemetry.ts
  • apps/server/src/providers/index.ts
  • apps/server/src/providers/registry.ts
  • apps/server/src/routes/providers/common.ts
  • apps/server/src/routes/providers/index.ts
  • apps/server/src/routes/providers/routes/list.ts
  • apps/server/src/routes/providers/routes/probe.ts
  • apps/server/src/routes/providers/routes/status.ts
  • apps/server/src/routes/providers/routes/test.ts
  • apps/server/src/services/agent-monitor.ts
💤 Files with no reviewable changes (1)
  • apps/server/src/lib/beads-validation.ts
🧰 Additional context used
📓 Path-based instructions (3)
apps/server/src/routes/**

📄 CodeRabbit inference engine (CLAUDE.md)

API routes should be placed in apps/server/src/routes/, with one file per route/resource

Files:

  • apps/server/src/routes/providers/index.ts
  • apps/server/src/routes/providers/common.ts
  • apps/server/src/routes/providers/routes/list.ts
  • apps/server/src/routes/providers/routes/probe.ts
  • apps/server/src/routes/providers/routes/status.ts
  • apps/server/src/routes/providers/routes/test.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx,js,jsx}: Run type checking with npm run typecheck before syncing the Beads database as part of quality gates
Run linting with npm run lint before syncing the Beads database as part of quality gates

Files:

  • apps/server/src/routes/providers/index.ts
  • apps/server/src/routes/providers/common.ts
  • apps/server/src/routes/providers/routes/list.ts
  • apps/server/src/routes/providers/routes/probe.ts
  • apps/server/src/providers/claude-provider.ts
  • apps/server/src/routes/providers/routes/status.ts
  • apps/server/src/lib/telemetry.ts
  • apps/server/src/providers/cursor-telemetry.ts
  • apps/server/src/providers/index.ts
  • apps/server/src/providers/cursor-provider.ts
  • apps/server/src/services/agent-monitor.ts
  • apps/server/src/routes/providers/routes/test.ts
  • apps/server/src/providers/claude-telemetry.ts
  • apps/server/src/lib/telemetry-factory.ts
  • apps/server/src/providers/registry.ts
  • apps/server/src/providers/auth-cache.ts
  • apps/server/src/providers/capability-probe.ts
apps/server/src/services/**

📄 CodeRabbit inference engine (CLAUDE.md)

Services should be placed in apps/server/src/services/, with one service per file

Files:

  • apps/server/src/services/agent-monitor.ts
🧠 Learnings (1)
📚 Learning: 2025-12-24T19:31:56.698Z
Learnt from: CR
Repo: 0xtsotsi/DevFlow PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-24T19:31:56.698Z
Learning: Run linting and tests on server changes with `npm run lint --workspace=apps/server` and `npm run test:server`

Applied to files:

  • .github/workflows/provider-check.yml
🧬 Code graph analysis (11)
apps/server/src/routes/providers/index.ts (4)
apps/server/src/routes/providers/routes/list.ts (1)
  • createListProvidersHandler (25-64)
apps/server/src/routes/providers/routes/status.ts (1)
  • createStatusHandler (9-53)
apps/server/src/routes/providers/routes/test.ts (1)
  • createTestProviderHandler (10-54)
apps/server/src/routes/providers/routes/probe.ts (2)
  • createProbeProviderHandler (10-43)
  • createProbeAllProvidersHandler (48-74)
apps/server/src/routes/providers/common.ts (1)
apps/server/src/routes/common.ts (1)
  • createLogError (34-38)
apps/server/src/routes/providers/routes/list.ts (3)
apps/server/src/providers/index.ts (1)
  • EngineRegistry (18-18)
apps/server/src/providers/registry.ts (1)
  • EngineRegistry (362-362)
apps/server/src/routes/providers/common.ts (1)
  • logError (12-12)
apps/server/src/routes/providers/routes/probe.ts (3)
apps/server/src/providers/registry.ts (1)
  • EngineRegistry (362-362)
apps/server/src/providers/capability-probe.ts (1)
  • capabilityProbe (403-403)
apps/server/src/routes/providers/common.ts (1)
  • logError (12-12)
apps/server/src/providers/claude-provider.ts (3)
apps/server/src/lib/telemetry.ts (1)
  • TelemetryParser (43-43)
apps/server/src/providers/registry.ts (1)
  • TelemetryParser (23-25)
apps/server/src/providers/claude-telemetry.ts (1)
  • claudeTelemetryParser (236-236)
apps/server/src/routes/providers/routes/status.ts (3)
apps/server/src/providers/index.ts (1)
  • EngineRegistry (18-18)
apps/server/src/providers/registry.ts (1)
  • EngineRegistry (362-362)
apps/server/src/routes/providers/common.ts (1)
  • logError (12-12)
apps/server/src/providers/cursor-provider.ts (3)
apps/server/src/lib/telemetry.ts (1)
  • TelemetryParser (43-43)
apps/server/src/providers/registry.ts (1)
  • TelemetryParser (23-25)
apps/server/src/providers/cursor-telemetry.ts (1)
  • cursorTelemetryParser (286-286)
apps/server/src/providers/claude-telemetry.ts (3)
apps/server/src/lib/telemetry.ts (3)
  • ParsedTelemetry (22-35)
  • calculateCost (167-180)
  • TelemetryParser (43-43)
apps/server/src/providers/index.ts (5)
  • ParsedTelemetry (22-22)
  • createClaudeTelemetryParser (42-42)
  • TelemetryParser (21-21)
  • claudeTelemetryParser (41-41)
  • parseClaudeTelemetry (43-43)
apps/server/src/providers/registry.ts (2)
  • ParsedTelemetry (30-43)
  • TelemetryParser (23-25)
apps/server/src/lib/telemetry-factory.ts (5)
apps/server/src/lib/telemetry.ts (5)
  • TelemetryParser (43-43)
  • createTelemetryCapture (189-201)
  • CapturedTelemetry (51-64)
  • TelemetryCaptureOptions (69-78)
  • createEmptyTelemetry (211-222)
apps/server/src/providers/index.ts (3)
  • TelemetryParser (21-21)
  • claudeTelemetryParser (41-41)
  • cursorTelemetryParser (46-46)
apps/server/src/providers/registry.ts (1)
  • TelemetryParser (23-25)
apps/server/src/providers/claude-telemetry.ts (1)
  • claudeTelemetryParser (236-236)
apps/server/src/providers/cursor-telemetry.ts (1)
  • cursorTelemetryParser (286-286)
apps/server/src/providers/auth-cache.ts (1)
apps/server/src/providers/index.ts (3)
  • AuthCacheStats (37-37)
  • AuthCache (36-36)
  • authCache (36-36)
apps/server/src/providers/capability-probe.ts (1)
apps/server/src/providers/index.ts (7)
  • ProviderCapability (29-29)
  • ProviderLimits (31-31)
  • ProbeResult (30-30)
  • ProviderCapabilityProbe (27-27)
  • BaseProvider (8-8)
  • ModelDefinition (57-57)
  • capabilityProbe (27-27)
🪛 Biome (2.1.2)
apps/server/src/lib/telemetry-factory.ts

[error] 69-69: Shouldn't redeclare 'createTelemetryCapture'. Consider to delete it or rename it.

'createTelemetryCapture' is defined here:

(lint/suspicious/noRedeclare)

apps/server/src/providers/registry.ts

[error] 362-362: Shouldn't redeclare 'EngineRegistry'. Consider to delete it or rename it.

'EngineRegistry' is defined here:

(lint/suspicious/noRedeclare)


[error] 362-362: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)

🔇 Additional comments (21)
.github/workflows/provider-check.yml (1)

1-72: LGTM!

The workflow structure, trigger paths, and validation steps for provider exports and telemetry parsers are well-designed. The path filters correctly target the provider infrastructure files.

apps/server/src/providers/auth-cache.ts (1)

1-60: LGTM!

Well-structured cache implementation with comprehensive documentation, proper TTL handling, and useful utility methods. The Map-based storage provides the promised O(1) lookups.

apps/server/src/providers/capability-probe.ts (1)

26-57: LGTM!

Well-defined interfaces for ProviderCapability, ProviderLimits, and ProbeResult. The type definitions are comprehensive and will provide good type safety for capability probing across the codebase.

Also applies to: 70-79, 84-90

apps/server/src/routes/providers/index.ts (1)

1-24: LGTM!

Clean router factory pattern with proper imports and documentation. The factory approach enables easy testing and composition.

apps/server/src/routes/providers/common.ts (1)

1-12: LGTM!

Clean utility module that properly scopes a logger for provider routes and re-exports shared error handling utilities.

apps/server/src/routes/providers/routes/test.ts (1)

10-53: LGTM!

Good implementation of the test handler with proper validation, cache-aside pattern via authCache.getOrCompute, and registry update. The duck-typing check for isAuthenticated provides flexibility for different provider implementations.

apps/server/src/routes/providers/routes/status.ts (1)

9-52: LGTM!

Clean implementation providing comprehensive status information including primary provider, fallback chain, and aggregated counts. The response structure is well-organized for client consumption.

apps/server/src/routes/providers/routes/list.ts (1)

25-63: LGTM!

Well-structured handler that builds provider information including optional capabilities. The duck-typing pattern for getCapabilities is consistent with other handlers in this module.

apps/server/src/routes/providers/routes/probe.ts (2)

10-43: LGTM! Handler follows established patterns.

The single-provider probe handler validates input, handles not-found cases, and returns consistent JSON responses. Error handling with logError and getErrorMessage aligns with sibling route files.


48-74: LGTM! Bulk probe handler is well-structured.

The handler correctly aggregates all provider probes and returns a count alongside results. The Map-based provider lookup aligns with the capability probing system design.

apps/server/src/providers/cursor-telemetry.ts (2)

40-90: LGTM! JSON parsing handles multi-format output well.

The parser correctly aggregates tokens across JSON lines and handles Cursor-specific, Claude-standard, and direct token count formats. Silent catch for non-JSON lines is appropriate here.


240-281: LGTM! Factory pattern provides clean API.

The parser factory correctly prioritizes JSON parsing over text fallback and enriches results with duration and metadata when available.

apps/server/src/providers/claude-provider.ts (1)

22-23: LGTM! Authentication and telemetry integration are clean.

The telemetryParser property and isAuthenticated() method follow the same pattern as the Cursor provider, enabling consistent capability probing across providers.

Also applies to: 29-37

apps/server/src/providers/index.ts (1)

39-49: LGTM! Telemetry parser exports are well-organized.

Both Claude and Cursor telemetry parsers are properly exposed with their factory functions and convenience wrappers.

apps/server/src/providers/claude-telemetry.ts (1)

44-88: LGTM! JSON parsing is robust.

The parser handles both message-wrapped and direct usage objects, correctly aggregating tokens across multiple JSON lines.

apps/server/src/lib/telemetry-factory.ts (1)

197-270: LGTM! TelemetryCapture class provides good lifecycle management.

The class encapsulates parsing state and allows parser overrides, which is useful for testing and custom integrations.

apps/server/src/lib/telemetry.ts (1)

22-35: LGTM! Core telemetry types are well-designed.

The ParsedTelemetry, TelemetryParser, CapturedTelemetry, and TelemetryCaptureOptions interfaces provide a clean abstraction for unified telemetry handling across providers.

Also applies to: 43-43, 51-64, 69-78

apps/server/src/providers/cursor-provider.ts (1)

34-84: LGTM! Model definitions are comprehensive.

The CURSOR_MODELS array provides well-structured model definitions with appropriate metadata for the Cursor-hosted Claude models.

apps/server/src/providers/registry.ts (1)

1-17: LGTM on overall structure.

The registry design with Map-based O(1) lookups, lifecycle hooks, and convenience exports is well-structured. The interface definitions are clear and the singleton pattern is appropriate for this use case.

Also applies to: 370-390

apps/server/src/services/agent-monitor.ts (2)

143-172: LGTM on schema design.

Good use of indexes on frequently queried columns (status, parent_id, feature_id, beads_id, pid, created_at). WAL mode is appropriate for concurrent read/write scenarios.


22-106: LGTM on type definitions.

The AgentStatus, AgentRecord, AgentTreeNode, and AgentStats interfaces are well-defined with clear documentation and appropriate use of optional fields.

Comment on lines +137 to +164
- name: Test provider registry operations
shell: bash
run: |
node --run << 'EOF'
import { EngineRegistry } from './apps/server/src/providers/registry.js';
import { ClaudeProvider } from './apps/server/src/providers/claude-provider.js';

// Test registration
const claude = new ClaudeProvider();
const metadata = EngineRegistry.register('test-claude', claude, {});

console.log('✓ Provider registered:', metadata.id);

// Test retrieval
const retrieved = EngineRegistry.get('test-claude');
if (!retrieved || retrieved.id !== 'test-claude') {
throw new Error('Provider retrieval failed');
}
console.log('✓ Provider retrieved successfully');

// Test getAll
const all = EngineRegistry.getAll();
console.log('✓ Total providers registered:', all.length);

// Cleanup
EngineRegistry.unregister('test-claude');
console.log('✓ Provider registry integration test passed');
EOF
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Invalid node --run syntax will cause CI failure.

The node --run flag is used to execute scripts defined in package.json, not to run inline JavaScript code. This integration test step will fail.

🔎 Proposed fix using proper ESM execution
      - name: Test provider registry operations
        shell: bash
        run: |
-          node --run << 'EOF'
-          import { EngineRegistry } from './apps/server/src/providers/registry.js';
-          import { ClaudeProvider } from './apps/server/src/providers/claude-provider.js';
-
-          // Test registration
-          const claude = new ClaudeProvider();
-          const metadata = EngineRegistry.register('test-claude', claude, {});
-
-          console.log('✓ Provider registered:', metadata.id);
-
-          // Test retrieval
-          const retrieved = EngineRegistry.get('test-claude');
-          if (!retrieved || retrieved.id !== 'test-claude') {
-            throw new Error('Provider retrieval failed');
-          }
-          console.log('✓ Provider retrieved successfully');
-
-          // Test getAll
-          const all = EngineRegistry.getAll();
-          console.log('✓ Total providers registered:', all.length);
-
-          // Cleanup
-          EngineRegistry.unregister('test-claude');
-          console.log('✓ Provider registry integration test passed');
-          EOF
+          node --input-type=module -e "
+          import { EngineRegistry } from './apps/server/src/providers/registry.js';
+          import { ClaudeProvider } from './apps/server/src/providers/claude-provider.js';
+
+          // Test registration
+          const claude = new ClaudeProvider();
+          const metadata = EngineRegistry.register('test-claude', claude, {});
+
+          console.log('✓ Provider registered:', metadata.id);
+
+          // Test retrieval
+          const retrieved = EngineRegistry.get('test-claude');
+          if (!retrieved || retrieved.id !== 'test-claude') {
+            throw new Error('Provider retrieval failed');
+          }
+          console.log('✓ Provider retrieved successfully');
+
+          // Test getAll
+          const all = EngineRegistry.getAll();
+          console.log('✓ Total providers registered:', all.length);
+
+          // Cleanup
+          EngineRegistry.unregister('test-claude');
+          console.log('✓ Provider registry integration test passed');
+          "
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- name: Test provider registry operations
shell: bash
run: |
node --run << 'EOF'
import { EngineRegistry } from './apps/server/src/providers/registry.js';
import { ClaudeProvider } from './apps/server/src/providers/claude-provider.js';
// Test registration
const claude = new ClaudeProvider();
const metadata = EngineRegistry.register('test-claude', claude, {});
console.log('✓ Provider registered:', metadata.id);
// Test retrieval
const retrieved = EngineRegistry.get('test-claude');
if (!retrieved || retrieved.id !== 'test-claude') {
throw new Error('Provider retrieval failed');
}
console.log('✓ Provider retrieved successfully');
// Test getAll
const all = EngineRegistry.getAll();
console.log('✓ Total providers registered:', all.length);
// Cleanup
EngineRegistry.unregister('test-claude');
console.log('✓ Provider registry integration test passed');
EOF
- name: Test provider registry operations
shell: bash
run: |
node --input-type=module -e "
import { EngineRegistry } from './apps/server/src/providers/registry.js';
import { ClaudeProvider } from './apps/server/src/providers/claude-provider.js';
// Test registration
const claude = new ClaudeProvider();
const metadata = EngineRegistry.register('test-claude', claude, {});
console.log('✓ Provider registered:', metadata.id);
// Test retrieval
const retrieved = EngineRegistry.get('test-claude');
if (!retrieved || retrieved.id !== 'test-claude') {
throw new Error('Provider retrieval failed');
}
console.log('✓ Provider retrieved successfully');
// Test getAll
const all = EngineRegistry.getAll();
console.log('✓ Total providers registered:', all.length);
// Cleanup
EngineRegistry.unregister('test-claude');
console.log('✓ Provider registry integration test passed');
"
🤖 Prompt for AI Agents
.github/workflows/provider-check.yml around lines 137 to 164: the step uses the
invalid `node --run` flag which will fail; replace `node --run << 'EOF'` with
`node --input-type=module << 'EOF'` so the heredoc is executed as ESM (allowing
top-level import), keep the existing inline script and EOF markers unchanged,
and ensure the imported paths are valid ESM module paths.

Comment on lines +167 to +180
export function calculateCost(
tokensIn: number,
tokensOut: number,
cached: number,
model: string
): number {
const pricing = getPricingForModel(model);

const inputCost = (tokensIn - cached) * (pricing.inputPrice / 1_000_000);
const outputCost = tokensOut * (pricing.outputPrice / 1_000_000);
const cachedCost = cached * ((pricing.cachedPrice ?? pricing.inputPrice * 0.1) / 1_000_000);

return inputCost + outputCost + cachedCost;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Potential negative cost if cached tokens exceed input tokens.

Line 175 computes inputCost = (tokensIn - cached) * pricing.inputPrice / 1_000_000. If cached > tokensIn (which shouldn't happen normally but could occur with malformed data), this produces a negative cost.

Consider adding a guard:

Suggested fix
-  const inputCost = (tokensIn - cached) * (pricing.inputPrice / 1_000_000);
+  const inputCost = Math.max(0, tokensIn - cached) * (pricing.inputPrice / 1_000_000);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export function calculateCost(
tokensIn: number,
tokensOut: number,
cached: number,
model: string
): number {
const pricing = getPricingForModel(model);
const inputCost = (tokensIn - cached) * (pricing.inputPrice / 1_000_000);
const outputCost = tokensOut * (pricing.outputPrice / 1_000_000);
const cachedCost = cached * ((pricing.cachedPrice ?? pricing.inputPrice * 0.1) / 1_000_000);
return inputCost + outputCost + cachedCost;
}
export function calculateCost(
tokensIn: number,
tokensOut: number,
cached: number,
model: string
): number {
const pricing = getPricingForModel(model);
const inputCost = Math.max(0, tokensIn - cached) * (pricing.inputPrice / 1_000_000);
const outputCost = tokensOut * (pricing.outputPrice / 1_000_000);
const cachedCost = cached * ((pricing.cachedPrice ?? pricing.inputPrice * 0.1) / 1_000_000);
return inputCost + outputCost + cachedCost;
}
🤖 Prompt for AI Agents
In apps/server/src/lib/telemetry.ts around lines 167 to 180, the input cost
calculation can go negative if cached > tokensIn; clamp the cached value used in
cost math to at most tokensIn (e.g., effectiveCached = Math.min(cached,
tokensIn)) and use that effectiveCached when computing inputCost and cachedCost
so inputCost remains non-negative and cachedCost does not double-count beyond
tokensIn.

Comment on lines 117 to 158
async *executeQuery(options: ExecuteOptions): AsyncGenerator<ProviderMessage> {
const { prompt, model, cwd, systemPrompt } = options;

// Build Cursor CLI arguments
const args = this.buildCursorArgs(model, systemPrompt);

// Add prompt as argument
if (typeof prompt === 'string') {
args.push(prompt);
}

try {
// Run the command and get output
const output = await this.runCursorCommand(args);

// Yield the output as an assistant message
yield {
type: 'assistant',
message: {
role: 'assistant',
content: [
{
type: 'text',
text: output,
},
],
},
};

// Yield final result
yield {
type: 'result',
subtype: 'success',
result: output,
};
} catch (error) {
yield {
type: 'error',
error: (error as Error).message,
};
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Array prompts (multi-part with images) are silently ignored.

The executeQuery method only handles string prompts (lines 124-126). If prompt is an array (multi-part with images per the ExecuteOptions type), it's not added to the CLI args, resulting in a command executed without a prompt.

Consider throwing an error or logging a warning for unsupported prompt types:

Suggested fix
     // Add prompt as argument
     if (typeof prompt === 'string') {
       args.push(prompt);
+    } else if (Array.isArray(prompt)) {
+      // Cursor CLI doesn't support multi-part prompts
+      throw new Error('Cursor CLI does not support multi-part prompts with images');
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async *executeQuery(options: ExecuteOptions): AsyncGenerator<ProviderMessage> {
const { prompt, model, cwd, systemPrompt } = options;
// Build Cursor CLI arguments
const args = this.buildCursorArgs(model, systemPrompt);
// Add prompt as argument
if (typeof prompt === 'string') {
args.push(prompt);
}
try {
// Run the command and get output
const output = await this.runCursorCommand(args);
// Yield the output as an assistant message
yield {
type: 'assistant',
message: {
role: 'assistant',
content: [
{
type: 'text',
text: output,
},
],
},
};
// Yield final result
yield {
type: 'result',
subtype: 'success',
result: output,
};
} catch (error) {
yield {
type: 'error',
error: (error as Error).message,
};
}
}
async *executeQuery(options: ExecuteOptions): AsyncGenerator<ProviderMessage> {
const { prompt, model, cwd, systemPrompt } = options;
// Build Cursor CLI arguments
const args = this.buildCursorArgs(model, systemPrompt);
// Add prompt as argument
if (typeof prompt === 'string') {
args.push(prompt);
} else if (Array.isArray(prompt)) {
// Cursor CLI doesn't support multi-part prompts
throw new Error('Cursor CLI does not support multi-part prompts with images');
}
try {
// Run the command and get output
const output = await this.runCursorCommand(args);
// Yield the output as an assistant message
yield {
type: 'assistant',
message: {
role: 'assistant',
content: [
{
type: 'text',
text: output,
},
],
},
};
// Yield final result
yield {
type: 'result',
subtype: 'success',
result: output,
};
} catch (error) {
yield {
type: 'error',
error: (error as Error).message,
};
}
}
🤖 Prompt for AI Agents
In apps/server/src/providers/cursor-provider.ts around lines 117 to 158, the
executeQuery currently only handles string prompts and silently ignores array
prompts; update the method to detect when prompt is an array and handle it
explicitly: if the array contains any non-text parts (e.g., images) throw or
yield an error informing the caller that multi-part/image prompts are
unsupported by the Cursor CLI, otherwise join all text segments (preserving
order and spacing) into a single string and push that into args; also add a
debug warning log when converting an array of text segments so the behavior is
visible.

Comment on lines +29 to +33
// Probe a specific provider's capabilities
router.post('/probe/:providerId', createProbeProviderHandler());

// Probe all providers
router.post('/probe/all', createProbeAllProvidersHandler());
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Route ordering bug: /probe/all will never be matched.

Express matches routes in registration order. Since /probe/:providerId is registered before /probe/all, a request to /probe/all will be caught by the parameterized route with providerId = "all".

🔎 Proposed fix: Register specific route before parameterized route
   // Probe a specific provider's capabilities
-  router.post('/probe/:providerId', createProbeProviderHandler());
-
-  // Probe all providers
   router.post('/probe/all', createProbeAllProvidersHandler());
+
+  // Probe a specific provider's capabilities
+  router.post('/probe/:providerId', createProbeProviderHandler());

   return router;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Probe a specific provider's capabilities
router.post('/probe/:providerId', createProbeProviderHandler());
// Probe all providers
router.post('/probe/all', createProbeAllProvidersHandler());
// Probe all providers
router.post('/probe/all', createProbeAllProvidersHandler());
// Probe a specific provider's capabilities
router.post('/probe/:providerId', createProbeProviderHandler());
🤖 Prompt for AI Agents
In apps/server/src/routes/providers/index.ts around lines 29-33, the
parameterized route router.post('/probe/:providerId', ...) is registered before
the specific router.post('/probe/all', ...), causing '/probe/all' to be captured
as providerId="all"; fix by registering the specific '/probe/all' route before
the '/probe/:providerId' route (i.e., move the router.post('/probe/all',
createProbeAllProvidersHandler()) line above the
router.post('/probe/:providerId', createProbeProviderHandler()) line) so the
literal route is matched first.

Comment on lines +113 to +121
export class AgentMonitorService {
private db!: Database.Database;
private dbPath: string;
private pidCheckInterval?: NodeJS.Timeout;
private pidCheckIntervalMs = 30000; // 30 seconds

constructor(dataDir: string) {
this.dbPath = path.join(dataDir, 'agents.db');
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Database access before initialize() will throw.

The db property uses definite assignment assertion (!), but if any method (e.g., register(), getAgent()) is called before initialize(), it will throw an undefined access error. Consider adding a guard or lazy initialization.

🔎 Proposed fix: Add initialization guard
+  private initialized = false;
+
+  private ensureInitialized(): void {
+    if (!this.initialized) {
+      throw new Error('AgentMonitorService not initialized. Call initialize() first.');
+    }
+  }
+
   async initialize(): Promise<void> {
     await ensureDir(path.dirname(this.dbPath));
 
     this.db = new Database(this.dbPath);
     this.db.pragma('journal_mode = WAL');
 
     this.createSchema();
+    this.initialized = true;
 
     // Start PID monitoring
     this.startPIDMonitoring();
   }

Then call this.ensureInitialized() at the start of methods that use this.db.

Also applies to: 128-138

🤖 Prompt for AI Agents
In apps/server/src/services/agent-monitor.ts around lines 113-121 (and similarly
128-138), the class uses a definite-assigned this.db which will be undefined if
methods are called before initialize(); add a private async ensureInitialized()
that initializes/open the database if not already set (or throws a clear error),
then call await this.ensureInitialized() at the start of all public methods that
access this.db (e.g., register(), getAgent(), etc.); alternatively change to
lazy-initialize the DB in ensureInitialized and remove the definite-assignment
operator to prevent undefined access.

Comment on lines +638 to +668
private rowToAgent(row: unknown): AgentRecord | null {
if (!row || typeof row !== 'object') {
return null;
}

const r = row as Record<string, unknown>;

return {
id: String(r.id ?? ''),
parentId: r.parent_id ? String(r.parent_id) : null,
status: (r.status ?? 'pending') as AgentStatus,
engine: String(r.engine ?? ''),
model: String(r.model ?? ''),
pid: r.pid !== null ? Number(r.pid) : null,
workingDir: String(r.working_dir ?? ''),
featureId: r.feature_id ? String(r.feature_id) : null,
beadsId: r.beads_id ? String(r.beads_id) : null,
prompt: String(r.prompt ?? ''),
createdAt: Number(r.created_at ?? 0),
startedAt: r.started_at !== null ? Number(r.started_at) : null,
completedAt: r.completed_at !== null ? Number(r.completed_at) : null,
error: r.error !== null ? String(r.error) : null,
telemetry: r.telemetry_json
? (JSON.parse(String(r.telemetry_json)) as ParsedTelemetry)
: null,
sessionId: r.session_id ? String(r.session_id) : null,
metadata: r.metadata_json
? (JSON.parse(String(r.metadata_json)) as Record<string, unknown>)
: null,
};
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

JSON.parse can throw on malformed data—missing error handling.

If telemetry_json or metadata_json columns contain invalid JSON (e.g., due to data corruption or schema migration issues), JSON.parse will throw and crash the entire query operation.

🔎 Proposed fix: Wrap JSON parsing in try-catch
+  private safeJsonParse<T>(json: string | null | undefined): T | null {
+    if (!json) return null;
+    try {
+      return JSON.parse(json) as T;
+    } catch {
+      console.warn('[AgentMonitor] Failed to parse JSON:', json.substring(0, 100));
+      return null;
+    }
+  }
+
   private rowToAgent(row: unknown): AgentRecord | null {
     // ... existing validation ...
 
     return {
       // ... other fields ...
-      telemetry: r.telemetry_json
-        ? (JSON.parse(String(r.telemetry_json)) as ParsedTelemetry)
-        : null,
+      telemetry: this.safeJsonParse<ParsedTelemetry>(r.telemetry_json as string),
       sessionId: r.session_id ? String(r.session_id) : null,
-      metadata: r.metadata_json
-        ? (JSON.parse(String(r.metadata_json)) as Record<string, unknown>)
-        : null,
+      metadata: this.safeJsonParse<Record<string, unknown>>(r.metadata_json as string),
     };
   }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In apps/server/src/services/agent-monitor.ts around lines 638 to 668, the
JSON.parse calls for telemetry_json and metadata_json can throw on malformed
data; update rowToAgent to guard parsing by wrapping each JSON.parse in a
try-catch (separately for telemetry_json and metadata_json), return null for
that field if parsing fails (or optionally log the parsing error using the
service logger), and ensure no exceptions propagate out of rowToAgent so a
single bad row cannot crash the query operation.

Address high-priority and medium-priority issues from PR review:

**High Priority Fixes:**
- Fix P1: telemetry-factory.ts import collision - alias imported createTelemetryCapture as createTelemetryCaptureUtil
- Fix P2: Pass cwd to Cursor CLI spawn - add cwd parameter to runCursorCommand and pass to spawn
- Fix: Cursor auth check fragility - add --help command test to verify CLI functionality
- Fix: Race condition in addTelemetry - use atomic SQL UPDATE with json_set for aggregation
- Fix: generateTelemetryId uniqueness - use crypto.randomUUID() instead of Date.now() + Math.random()
- Fix: Hardcoded logic in capability probe - move auth/rate limit logic to provider classes via getAuthenticationMethods() and getRateLimits()
- Fix: Hardcoded rate limits in probe - delegate to provider.getRateLimits()
- Fix: Incomplete cleanupOrphanedPIDs - implement full PID checking loop

**Medium Priority Fixes:**
- Fix: Remove unused capabilityProbe import from list.ts route
- Fix: Add getCapabilities? optional method to BaseProvider for better type safety
- Fix: Derive maxContextWindow/maxOutputTokens dynamically from available models

**Architecture Improvements:**
- Add AuthMethod and RateLimit types to BaseProvider
- Add getAuthenticationMethods() and getRateLimits() abstract methods to BaseProvider
- Implement provider-specific methods in ClaudeProvider and CursorProvider

Addresses comments: gemini-code-assist[bot] #2649131671-2649131689

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/server/src/providers/claude-provider.ts (1)

191-241: Update Claude Sonnet 4 maxOutputTokens from 16,000 to 64,000.

Per Anthropic documentation, Claude Sonnet 4 supports up to 64,000 output tokens, not the 16,000 specified in the code. Update the value to match current specifications.

♻️ Duplicate comments (6)
apps/server/src/lib/telemetry-factory.ts (1)

91-93: Consider structured logging instead of console.warn.

Using console.warn in a server application lacks structure and environment-specific configurability. Consider using a dedicated logger for better observability.

Also applies to: 139-140

apps/server/src/providers/capability-probe.ts (1)

336-344: getCacheStats() returns placeholder values.

The method returns hits: 0, misses: 0 despite the comment acknowledging this limitation. For production observability, consider implementing actual hit/miss counters or removing this method to avoid misleading callers.

apps/server/src/services/agent-monitor.ts (3)

668-698: JSON.parse can throw on malformed data.

If telemetry_json or metadata_json contain invalid JSON (due to corruption or migration issues), JSON.parse will throw and crash the entire query. Wrap parsing in try-catch to handle gracefully.


113-121: Database access before initialize() will throw.

The db property uses definite assignment (!), but methods like register() or getAgent() called before initialize() will cause undefined access errors. Consider adding an initialization guard.


813-818: Accessing private property via bracket notation breaks encapsulation.

agentMonitorInstance['dbPath'] accesses a private property, which is fragile and bypasses TypeScript's access control. Consider exposing a public getter or managing the singleton differently.

apps/server/src/lib/telemetry.ts (1)

169-182: Potential negative cost if cached > tokensIn.

Line 177 computes (tokensIn - cached) which produces a negative value if cached exceeds tokensIn (e.g., due to malformed data). This would result in incorrect cost calculation.

🔎 Proposed fix
-  const inputCost = (tokensIn - cached) * (pricing.inputPrice / 1_000_000);
+  const inputCost = Math.max(0, tokensIn - cached) * (pricing.inputPrice / 1_000_000);
🧹 Nitpick comments (4)
apps/server/src/providers/claude-provider.ts (1)

89-94: Rate limits are hardcoded and tier-dependent.

The values requestsPerMinute: 50 and concurrent: 5 are fixed, but Anthropic API rate limits vary by tier (Tier 1 through Tier 4). Consider documenting that these represent a conservative baseline or exposing a way to configure them.

apps/server/src/lib/telemetry.ts (1)

24-45: Type duplication with registry.ts.

ParsedTelemetry and TelemetryParser are defined identically here and in apps/server/src/providers/registry.ts (per relevant snippets). Consider consolidating to a single source of truth to avoid drift.

apps/server/src/lib/telemetry-factory.ts (1)

30-34: Mutable module-level state could cause issues.

PROVIDER_PARSERS is module-level mutable state. While clearTelemetryParsers() is documented for testing, if called inadvertently in production, it would remove all parsers including the built-in ones (claude, anthropic, cursor). Consider reinitializing with defaults rather than clearing entirely.

🔎 Proposed fix
+const DEFAULT_PARSERS: Record<string, TelemetryParser> = {
+  claude: claudeTelemetryParser,
+  anthropic: claudeTelemetryParser,
+  cursor: cursorTelemetryParser,
+};
+
 const PROVIDER_PARSERS: Record<string, TelemetryParser> = {
-  claude: claudeTelemetryParser,
-  anthropic: claudeTelemetryParser,
-  cursor: cursorTelemetryParser,
+  ...DEFAULT_PARSERS,
 };

 export function clearTelemetryParsers(): void {
   for (const key of Object.keys(PROVIDER_PARSERS)) {
     delete PROVIDER_PARSERS[key];
   }
+  // Optionally restore defaults:
+  // Object.assign(PROVIDER_PARSERS, DEFAULT_PARSERS);
 }

Also applies to: 185-189

apps/server/src/providers/capability-probe.ts (1)

300-313: probeAll silently swallows probe failures.

When a probe fails, the error is logged but the provider is simply omitted from results. Callers have no way to know which providers failed. Consider including failure information in the result map.

🔎 Proposed enhancement
+export interface ProbeResult {
+  providerId: string;
+  capability: ProviderCapability;
+  limits: ProviderLimits;
+  timestamp: number;
+  cacheHit: boolean;
+  error?: string;  // Add error field
+}

 async probeAll(providers: Map<string, BaseProvider>): Promise<Map<string, ProbeResult>> {
   const results = new Map<string, ProbeResult>();

   for (const [id, provider] of providers.entries()) {
     try {
       const result = await this.probeWithResult(id, provider);
       results.set(id, result);
     } catch (error) {
       console.error(`[CapabilityProbe] Failed to probe provider "${id}":`, error);
+      results.set(id, {
+        providerId: id,
+        capability: DEFAULT_CAPABILITY,
+        limits: DEFAULT_LIMITS,
+        timestamp: Date.now(),
+        cacheHit: false,
+        error: error instanceof Error ? error.message : String(error),
+      });
     }
   }

   return results;
 }
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ed1f76a and d6e1e6e.

📒 Files selected for processing (8)
  • apps/server/src/lib/telemetry-factory.ts
  • apps/server/src/lib/telemetry.ts
  • apps/server/src/providers/base-provider.ts
  • apps/server/src/providers/capability-probe.ts
  • apps/server/src/providers/claude-provider.ts
  • apps/server/src/providers/cursor-provider.ts
  • apps/server/src/routes/providers/routes/list.ts
  • apps/server/src/services/agent-monitor.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/server/src/providers/cursor-provider.ts
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx,js,jsx}: Run type checking with npm run typecheck before syncing the Beads database as part of quality gates
Run linting with npm run lint before syncing the Beads database as part of quality gates

Files:

  • apps/server/src/providers/base-provider.ts
  • apps/server/src/providers/claude-provider.ts
  • apps/server/src/routes/providers/routes/list.ts
  • apps/server/src/providers/capability-probe.ts
  • apps/server/src/lib/telemetry-factory.ts
  • apps/server/src/services/agent-monitor.ts
  • apps/server/src/lib/telemetry.ts
apps/server/src/routes/**

📄 CodeRabbit inference engine (CLAUDE.md)

API routes should be placed in apps/server/src/routes/, with one file per route/resource

Files:

  • apps/server/src/routes/providers/routes/list.ts
apps/server/src/services/**

📄 CodeRabbit inference engine (CLAUDE.md)

Services should be placed in apps/server/src/services/, with one service per file

Files:

  • apps/server/src/services/agent-monitor.ts
🧬 Code graph analysis (3)
apps/server/src/providers/base-provider.ts (1)
apps/server/src/providers/capability-probe.ts (1)
  • AuthMethod (21-21)
apps/server/src/routes/providers/routes/list.ts (3)
apps/server/src/providers/index.ts (1)
  • EngineRegistry (18-18)
apps/server/src/providers/registry.ts (1)
  • EngineRegistry (362-362)
apps/server/src/routes/providers/common.ts (1)
  • logError (12-12)
apps/server/src/lib/telemetry.ts (3)
apps/server/src/providers/index.ts (2)
  • ParsedTelemetry (22-22)
  • TelemetryParser (21-21)
apps/server/src/providers/registry.ts (2)
  • ParsedTelemetry (30-43)
  • TelemetryParser (23-25)
apps/server/src/lib/telemetry-factory.ts (1)
  • createTelemetryCapture (69-99)
🔇 Additional comments (9)
apps/server/src/providers/base-provider.ts (2)

14-26: Well-designed type definitions for authentication and rate limiting.

The AuthMethod type and RateLimit interface provide a clean, extensible contract for providers. The optional fields in RateLimit allow providers to report only the limits they know about.


64-100: LGTM — sensible default implementations for provider capabilities.

The base class provides safe defaults (['none'] for auth, undefined for rate limits and capabilities) that subclasses can override. Making getCapabilities optional with ? is appropriate since not all providers may implement it.

apps/server/src/providers/claude-provider.ts (1)

58-74: Good fix: capabilities now derived dynamically from model definitions.

The getCapabilities() method correctly derives maxContextWindow and maxOutputTokens from getAvailableModels(), addressing the previous review feedback about hardcoded values.

apps/server/src/lib/telemetry.ts (1)

233-235: Good: Uses crypto.randomUUID() for guaranteed uniqueness.

This addresses the previous review concern about using Date.now() and Math.random() for ID generation.

apps/server/src/lib/telemetry-factory.ts (1)

17-21: Good fix: Import alias resolves name collision.

The import createTelemetryCapture as createTelemetryCaptureUtil properly avoids the previously flagged collision with the local createTelemetryCapture function.

apps/server/src/providers/capability-probe.ts (1)

146-149: Good fix: Capability detection now delegates to providers.

Both detectAuth() and probeLimits() now properly delegate to the provider's own methods (getAuthenticationMethods() and getRateLimits()), addressing the previous review feedback about hardcoded provider-specific logic.

Also applies to: 171-174

apps/server/src/routes/providers/routes/list.ts (2)

40-45: Type assertion for optional method is acceptable but could be cleaner.

The check if (metadata.provider.getCapabilities) followed by the type assertion works correctly since getCapabilities is optional on BaseProvider. The assertion is needed because TypeScript can't narrow the type through the property access check.


24-59: Route handler is well-structured.

The handler correctly uses the factory pattern, iterates providers, and handles errors appropriately. The capabilityProbe unused import issue from previous reviews has been resolved.

apps/server/src/services/agent-monitor.ts (1)

642-660: Good: cleanupOrphanedPIDs is now fully implemented.

The method now fetches running agents, checks process liveness, and updates dead agents to 'failed' status. This addresses the previous review feedback about the incomplete implementation.

Comment on lines +349 to +391
addTelemetry(id: string, telemetry: ParsedTelemetry): boolean {
// Use an atomic UPDATE with JSON extraction/aggregation
// This avoids the read-modify-write race condition
const stmt = this.db.prepare(`
UPDATE agents
SET telemetry_json = json_set(
COALESCE(telemetry_json, '{"tokensIn":0,"tokensOut":0,"cached":0,"cost":0,"duration":0}'),
'$.tokensIn',
COALESCE(json_extract(telemetry_json, '$.tokensIn'), 0) + ?
),
telemetry_json = json_set(
telemetry_json,
'$.tokensOut',
json_extract(telemetry_json, '$.tokensOut') + ?
),
telemetry_json = json_set(
telemetry_json,
'$.cached',
json_extract(telemetry_json, '$.cached') + ?
),
telemetry_json = json_set(
telemetry_json,
'$.cost',
json_extract(telemetry_json, '$.cost') + ?
),
telemetry_json = json_set(
telemetry_json,
'$.duration',
json_extract(telemetry_json, '$.duration') + ?
)
WHERE id = ?
`);

const result = stmt.run(
telemetry.tokensIn,
telemetry.tokensOut,
telemetry.cached,
telemetry.cost,
telemetry.duration,
id
);
return result.changes > 0;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Invalid SQL: Multiple assignments to the same column in a single UPDATE.

The SQL statement attempts to set telemetry_json five times in sequence. SQLite will only apply the last assignment, not aggregate all values. Each subsequent json_set uses telemetry_json as input, but the column isn't updated until the statement completes, so intermediate results are lost.

🔎 Proposed fix: Nest json_set calls
   addTelemetry(id: string, telemetry: ParsedTelemetry): boolean {
     const stmt = this.db.prepare(`
       UPDATE agents
-      SET telemetry_json = json_set(
-        COALESCE(telemetry_json, '{"tokensIn":0,"tokensOut":0,"cached":0,"cost":0,"duration":0}'),
-        '$.tokensIn',
-        COALESCE(json_extract(telemetry_json, '$.tokensIn'), 0) + ?
-      ),
-      telemetry_json = json_set(
-        telemetry_json,
-        '$.tokensOut',
-        json_extract(telemetry_json, '$.tokensOut') + ?
-      ),
-      telemetry_json = json_set(
-        telemetry_json,
-        '$.cached',
-        json_extract(telemetry_json, '$.cached') + ?
-      ),
-      telemetry_json = json_set(
-        telemetry_json,
-        '$.cost',
-        json_extract(telemetry_json, '$.cost') + ?
-      ),
-      telemetry_json = json_set(
-        telemetry_json,
-        '$.duration',
-        json_extract(telemetry_json, '$.duration') + ?
-      )
+      SET telemetry_json = json_set(
+        json_set(
+          json_set(
+            json_set(
+              json_set(
+                COALESCE(telemetry_json, '{"tokensIn":0,"tokensOut":0,"cached":0,"cost":0,"duration":0}'),
+                '$.tokensIn',
+                COALESCE(json_extract(telemetry_json, '$.tokensIn'), 0) + ?
+              ),
+              '$.tokensOut',
+              COALESCE(json_extract(telemetry_json, '$.tokensOut'), 0) + ?
+            ),
+            '$.cached',
+            COALESCE(json_extract(telemetry_json, '$.cached'), 0) + ?
+          ),
+          '$.cost',
+          COALESCE(json_extract(telemetry_json, '$.cost'), 0) + ?
+        ),
+        '$.duration',
+        COALESCE(json_extract(telemetry_json, '$.duration'), 0) + ?
+      )
       WHERE id = ?
     `);

@0xtsotsi 0xtsotsi merged commit d28978a into main Dec 27, 2025
1 check was pending
@0xtsotsi 0xtsotsi deleted the vk/1e60-machine-integrat branch January 5, 2026 18:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant