feat(#93): implement agent spawn via federation
Implements FED-010: Agent Spawn via Federation feature that enables spawning and managing Claude agents on remote federated Mosaic Stack instances via COMMAND message type. Features: - Federation agent command types (spawn, status, kill) - FederationAgentService for handling agent operations - Integration with orchestrator's agent spawner/lifecycle services - API endpoints for spawning, querying status, and killing agents - Full command routing through federation COMMAND infrastructure - Comprehensive test coverage (12/12 tests passing) Architecture: - Hub → Spoke: Spawn agents on remote instances - Command flow: FederationController → FederationAgentService → CommandService → Remote Orchestrator - Response handling: Remote orchestrator returns agent status/results - Security: Connection validation, signature verification Files created: - apps/api/src/federation/types/federation-agent.types.ts - apps/api/src/federation/federation-agent.service.ts - apps/api/src/federation/federation-agent.service.spec.ts Files modified: - apps/api/src/federation/command.service.ts (agent command routing) - apps/api/src/federation/federation.controller.ts (agent endpoints) - apps/api/src/federation/federation.module.ts (service registration) - apps/orchestrator/src/api/agents/agents.controller.ts (status endpoint) - apps/orchestrator/src/api/agents/agents.module.ts (lifecycle integration) Testing: - 12/12 tests passing for FederationAgentService - All command service tests passing - TypeScript compilation successful - Linting passed Refs #93 Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -26,7 +26,7 @@ describe.skipIf(!process.env.INTEGRATION_TESTS)("Semantic Search Integration", (
|
||||
// Initialize services
|
||||
prisma = new PrismaClient();
|
||||
const prismaService = prisma as unknown as PrismaService;
|
||||
|
||||
|
||||
// Mock cache service for testing
|
||||
cacheService = {
|
||||
getSearch: async () => null,
|
||||
@@ -37,11 +37,7 @@ describe.skipIf(!process.env.INTEGRATION_TESTS)("Semantic Search Integration", (
|
||||
} as unknown as KnowledgeCacheService;
|
||||
|
||||
embeddingService = new EmbeddingService(prismaService);
|
||||
searchService = new SearchService(
|
||||
prismaService,
|
||||
cacheService,
|
||||
embeddingService
|
||||
);
|
||||
searchService = new SearchService(prismaService, cacheService, embeddingService);
|
||||
|
||||
// Create test workspace and user
|
||||
const workspace = await prisma.workspace.create({
|
||||
@@ -84,10 +80,7 @@ describe.skipIf(!process.env.INTEGRATION_TESTS)("Semantic Search Integration", (
|
||||
const title = "Introduction to PostgreSQL";
|
||||
const content = "PostgreSQL is a powerful open-source database.";
|
||||
|
||||
const prepared = embeddingService.prepareContentForEmbedding(
|
||||
title,
|
||||
content
|
||||
);
|
||||
const prepared = embeddingService.prepareContentForEmbedding(title, content);
|
||||
|
||||
// Title should appear twice for weighting
|
||||
expect(prepared).toContain(title);
|
||||
@@ -122,10 +115,7 @@ describe.skipIf(!process.env.INTEGRATION_TESTS)("Semantic Search Integration", (
|
||||
it("should skip semantic search if OpenAI not configured", async () => {
|
||||
if (!embeddingService.isConfigured()) {
|
||||
await expect(
|
||||
searchService.semanticSearch(
|
||||
"database performance",
|
||||
testWorkspaceId
|
||||
)
|
||||
searchService.semanticSearch("database performance", testWorkspaceId)
|
||||
).rejects.toThrow();
|
||||
} else {
|
||||
// If configured, this is expected to work (tested below)
|
||||
@@ -156,10 +146,7 @@ describe.skipIf(!process.env.INTEGRATION_TESTS)("Semantic Search Integration", (
|
||||
entry.title,
|
||||
entry.content
|
||||
);
|
||||
await embeddingService.generateAndStoreEmbedding(
|
||||
created.id,
|
||||
preparedContent
|
||||
);
|
||||
await embeddingService.generateAndStoreEmbedding(created.id, preparedContent);
|
||||
}
|
||||
|
||||
// Wait a bit for embeddings to be stored
|
||||
@@ -175,9 +162,7 @@ describe.skipIf(!process.env.INTEGRATION_TESTS)("Semantic Search Integration", (
|
||||
expect(results.data.length).toBeGreaterThan(0);
|
||||
|
||||
// PostgreSQL entry should rank high for "relational database"
|
||||
const postgresEntry = results.data.find(
|
||||
(r) => r.slug === "postgresql-intro"
|
||||
);
|
||||
const postgresEntry = results.data.find((r) => r.slug === "postgresql-intro");
|
||||
expect(postgresEntry).toBeDefined();
|
||||
expect(postgresEntry!.rank).toBeGreaterThan(0);
|
||||
},
|
||||
@@ -187,18 +172,13 @@ describe.skipIf(!process.env.INTEGRATION_TESTS)("Semantic Search Integration", (
|
||||
it.skipIf(!process.env["OPENAI_API_KEY"])(
|
||||
"should perform hybrid search combining vector and keyword",
|
||||
async () => {
|
||||
const results = await searchService.hybridSearch(
|
||||
"indexing",
|
||||
testWorkspaceId
|
||||
);
|
||||
const results = await searchService.hybridSearch("indexing", testWorkspaceId);
|
||||
|
||||
// Should return results
|
||||
expect(results.data.length).toBeGreaterThan(0);
|
||||
|
||||
// Should find the indexing entry
|
||||
const indexingEntry = results.data.find(
|
||||
(r) => r.slug === "database-indexing"
|
||||
);
|
||||
const indexingEntry = results.data.find((r) => r.slug === "database-indexing");
|
||||
expect(indexingEntry).toBeDefined();
|
||||
},
|
||||
30000
|
||||
@@ -230,15 +210,10 @@ describe.skipIf(!process.env.INTEGRATION_TESTS)("Semantic Search Integration", (
|
||||
// Batch generate embeddings
|
||||
const entriesForEmbedding = entries.map((e) => ({
|
||||
id: e.id,
|
||||
content: embeddingService.prepareContentForEmbedding(
|
||||
e.title,
|
||||
e.content
|
||||
),
|
||||
content: embeddingService.prepareContentForEmbedding(e.title, e.content),
|
||||
}));
|
||||
|
||||
const successCount = await embeddingService.batchGenerateEmbeddings(
|
||||
entriesForEmbedding
|
||||
);
|
||||
const successCount = await embeddingService.batchGenerateEmbeddings(entriesForEmbedding);
|
||||
|
||||
expect(successCount).toBe(3);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user