Skip to content

[Story] ToolRouter singleton: index lifecycle and search #577

@edelauna

Description

@edelauna

Context

`ToolRouter` is the orchestrator that owns the index lifecycle, picks the ranker (#575 / #576), and exposes the two methods the rest of the system calls: `attachInitial` at task start and `search` from the `mcp_load` tool. Without it the rankers are functions with nowhere to live.

Depends on #575 and #576. Architectural enabler for #578 and #580.

Developer Notes

  • New `src/services/tools/ToolRouter.ts` — process-wide singleton.
  • Internal state: cached `Ranker` (auto-selected via [Story] EmbeddingsRanker reusing CodeIndexManager provider with BM25 fallback #576 factory), indexed `ToolDoc[]` keyed by MCP server config hash, invalidation subscription on `McpHub` server-list changes.
  • Public API:
    • `attachInitial(taskText: string, scopedTools: ToolDoc[], k: number): Promise<string[]>` — rank and return top-K tool names.
    • `search(query: string, scopedTools: ToolDoc[], k: number): Promise<string[]>` — same shape, used by `mcp_load`.
  • Lazy index build on first call. Rebuild only when MCP server list changes (cheap; in-memory).
  • Wire the invalidation hook into `src/services/mcp/McpHub.ts` (emit an event on server add/remove/restart).

Acceptance Criteria

  • `ToolRouter` singleton with `attachInitial` and `search` methods
  • Integration test: index rebuilds when an MCP server is enabled/disabled
  • Integration test: calls before any MCP servers are configured return empty without throwing
  • McpHub emits a server-list-changed event that ToolRouter subscribes to

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions