): UseWasmResult {
return () => {
cancelled = true;
};
- }, []); // eslint-disable-line react-hooks/exhaustive-deps
+ // loader is intentionally excluded: WASM modules are loaded once on mount.
+ // Pass a stable reference (e.g., a module-level const or useCallback) if reload is needed.
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, []);
return { module, loading, error };
}
diff --git a/packages/sdk/src/__tests__/sdk.test.ts b/packages/sdk/src/__tests__/sdk.test.ts
index befaabd..070629a 100644
--- a/packages/sdk/src/__tests__/sdk.test.ts
+++ b/packages/sdk/src/__tests__/sdk.test.ts
@@ -1,5 +1,4 @@
import { describe, it, expect } from 'vitest';
-import { describe as describePlugin } from 'vitest';
describe('@castquest/sdk', () => {
it('exports a valid module', async () => {
@@ -18,7 +17,7 @@ describe('@castquest/sdk', () => {
});
});
-describePlugin('Plugin system', () => {
+describe('Plugin system', () => {
it('Registry can register and list plugins', async () => {
const { Registry, AnalyticsPlugin } = await import('../plugins/index');
const registry = new Registry();
From 0d57ebc48875d77073fbfdfe9e0b258e77bbb03f Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 17 Mar 2026 09:09:12 +0000
Subject: [PATCH 6/8] Address PR review feedback: eslint parser dep, swarm
reliability, plugin async, layout a11y, sw.js strategy
Co-authored-by: SMSDAO <144380926+SMSDAO@users.noreply.github.com>
---
apps/web/app/layout.tsx | 5 --
apps/web/public/sw.js | 47 ++++++----
package.json | 1 +
packages/agents/src/__tests__/swarm.test.ts | 10 ++-
packages/agents/src/swarm.ts | 29 ++++--
packages/sdk/src/__tests__/sdk.test.ts | 24 +++--
packages/sdk/src/plugins/registry.ts | 7 +-
packages/sdk/src/plugins/types.ts | 4 +-
pnpm-lock.yaml | 99 +++++++++++++++++++++
9 files changed, 186 insertions(+), 40 deletions(-)
diff --git a/apps/web/app/layout.tsx b/apps/web/app/layout.tsx
index 7707e3f..ccb238e 100644
--- a/apps/web/app/layout.tsx
+++ b/apps/web/app/layout.tsx
@@ -16,17 +16,12 @@ export const metadata: Metadata = {
export const viewport: Viewport = {
width: 'device-width',
initialScale: 1,
- maximumScale: 1,
themeColor: '#22d3ee',
};
export default function RootLayout({ children }: { children: ReactNode }) {
return (
-
-
-
-
diff --git a/apps/web/public/sw.js b/apps/web/public/sw.js
index 41cbdfe..9013285 100644
--- a/apps/web/public/sw.js
+++ b/apps/web/public/sw.js
@@ -1,12 +1,15 @@
-const CACHE_NAME = 'castquest-v1';
-const STATIC_ASSETS = [
- '/',
- '/manifest.json',
-];
+// Cache name — increment the version when deploying new static assets
+const CACHE_VERSION = 'v1';
+const CACHE_NAME = `castquest-${CACHE_VERSION}`;
+
+// Only immutable Next.js static assets are served cache-first.
+// HTML pages and API routes use a network-first strategy so deployments
+// are picked up immediately without cache-busting issues.
+const IMMUTABLE_URL_PREFIX = '/_next/static/';
self.addEventListener('install', (event) => {
event.waitUntil(
- caches.open(CACHE_NAME).then((cache) => cache.addAll(STATIC_ASSETS))
+ caches.open(CACHE_NAME).then((cache) => cache.addAll(['/manifest.json']))
);
self.skipWaiting();
});
@@ -23,17 +26,27 @@ self.addEventListener('activate', (event) => {
self.addEventListener('fetch', (event) => {
if (event.request.method !== 'GET') return;
- event.respondWith(
- caches.match(event.request).then((cached) => {
- if (cached) return cached;
- return fetch(event.request).then((response) => {
- if (!response || response.status !== 200 || response.type !== 'basic') {
+ const url = new URL(event.request.url);
+
+ // Cache-first only for immutable Next.js static chunks
+ if (url.pathname.startsWith(IMMUTABLE_URL_PREFIX)) {
+ event.respondWith(
+ caches.match(event.request).then((cached) => {
+ if (cached) return cached;
+ return fetch(event.request).then((response) => {
+ if (response.ok) {
+ const cloned = response.clone();
+ caches.open(CACHE_NAME).then((cache) => cache.put(event.request, cloned));
+ }
return response;
- }
- const cloned = response.clone();
- caches.open(CACHE_NAME).then((cache) => cache.put(event.request, cloned));
- return response;
- });
- })
+ });
+ })
+ );
+ return;
+ }
+
+ // Network-first for HTML pages and everything else
+ event.respondWith(
+ fetch(event.request).catch(() => caches.match(event.request))
);
});
diff --git a/package.json b/package.json
index 80b2296..0935a9f 100644
--- a/package.json
+++ b/package.json
@@ -39,6 +39,7 @@
"orchestrate:v3": "pwsh ./infra/Orchestration.ps1"
},
"devDependencies": {
+ "@typescript-eslint/parser": "^8.57.1",
"eslint": "^8.55.0",
"eslint-plugin-security": "^2.1.0",
"turbo": "^1.11.2",
diff --git a/packages/agents/src/__tests__/swarm.test.ts b/packages/agents/src/__tests__/swarm.test.ts
index df2064b..8301c38 100644
--- a/packages/agents/src/__tests__/swarm.test.ts
+++ b/packages/agents/src/__tests__/swarm.test.ts
@@ -59,11 +59,17 @@ describe('Swarm orchestrator', () => {
await swarm.start();
const task = makeTask('price', { tokenAddress: '0xabc' });
const result = await swarm.dispatch(task);
- expect(result).not.toBeNull();
- expect(result?.success).toBe(true);
+ expect(result.success).toBe(true);
await swarm.stop();
});
+ it('returns an error result when no agents are registered', async () => {
+ const task = makeTask('price', {});
+ const result = await swarm.dispatch(task);
+ expect(result.success).toBe(false);
+ expect(result.error).toContain('No agents available');
+ });
+
it('processes a queue of tasks', async () => {
swarm.registerAgent(pricingAgent);
await swarm.start();
diff --git a/packages/agents/src/swarm.ts b/packages/agents/src/swarm.ts
index f7d050a..8d70eb4 100644
--- a/packages/agents/src/swarm.ts
+++ b/packages/agents/src/swarm.ts
@@ -45,10 +45,15 @@ export class Swarm {
}
}
- async dispatch(task: Task): Promise {
+ async dispatch(task: Task): Promise {
const agent = this.routeTask(task);
if (!agent) {
- return null;
+ return {
+ taskId: task.id,
+ success: false,
+ error: 'No agents available to handle the task',
+ completedAt: Date.now(),
+ };
}
return agent.execute(task);
}
@@ -60,14 +65,26 @@ export class Swarm {
async processQueue(): Promise {
const results: TaskResult[] = [];
- const maxConcurrency = this.config.maxConcurrency ?? 5;
while (this.taskQueue.length > 0 && this.running) {
- const batch = this.taskQueue.splice(0, maxConcurrency);
+ // Only dispatch to agents that are currently idle to avoid concurrent tasks on the same agent
+ const idleAgents = Array.from(this.agents.values()).filter(
+ (a) => a.getStatus() === 'idle'
+ );
+ if (idleAgents.length === 0) {
+ // No idle agents; wait briefly and retry
+ await new Promise((resolve) => setTimeout(resolve, 50));
+ continue;
+ }
+
+ const batchSize = Math.min(idleAgents.length, this.taskQueue.length);
+ const batch = this.taskQueue.splice(0, batchSize);
+
+ // Pair each task with a distinct idle agent
const batchResults = await Promise.all(
- batch.map((task) => this.dispatch(task))
+ batch.map((task, i) => idleAgents[i].execute(task))
);
- results.push(...batchResults.filter((r): r is TaskResult => r !== null));
+ results.push(...batchResults);
}
return results;
}
diff --git a/packages/sdk/src/__tests__/sdk.test.ts b/packages/sdk/src/__tests__/sdk.test.ts
index 070629a..2737b7e 100644
--- a/packages/sdk/src/__tests__/sdk.test.ts
+++ b/packages/sdk/src/__tests__/sdk.test.ts
@@ -22,7 +22,7 @@ describe('Plugin system', () => {
const { Registry, AnalyticsPlugin } = await import('../plugins/index');
const registry = new Registry();
const analytics = new AnalyticsPlugin();
- registry.register(analytics);
+ await registry.register(analytics);
expect(registry.listPlugins()).toHaveLength(1);
expect(registry.getPlugin('analytics')).toBe(analytics);
});
@@ -31,8 +31,7 @@ describe('Plugin system', () => {
const { Registry, AnalyticsPlugin } = await import('../plugins/index');
const registry = new Registry();
const analytics = new AnalyticsPlugin();
- await analytics.init(registry);
- registry.register(analytics);
+ await registry.register(analytics); // register() calls init() automatically
await registry.emit('onMint', { tokenId: 1, contract: '0x0', to: '0xabc' });
expect(analytics.getEventCount()).toBe(1);
});
@@ -41,12 +40,27 @@ describe('Plugin system', () => {
const { Registry, NotificationPlugin } = await import('../plugins/index');
const registry = new Registry();
const notifications = new NotificationPlugin();
- await notifications.init(registry);
- registry.register(notifications);
+ await registry.register(notifications); // register() calls init() automatically
await registry.emit('onMint', { tokenId: 42, contract: '0x0', to: '0xabc' });
const queue = notifications.getQueue();
expect(queue).toHaveLength(1);
expect(queue[0].type).toBe('mint');
});
+
+ it('Registry unregister awaits destroy and removes plugin', async () => {
+ const { Registry, AnalyticsPlugin } = await import('../plugins/index');
+ const registry = new Registry();
+ const analytics = new AnalyticsPlugin();
+ await registry.register(analytics);
+ await registry.unregister('analytics');
+ expect(registry.listPlugins()).toHaveLength(0);
+ });
+
+ it('Registry throws when registering a duplicate plugin name', async () => {
+ const { Registry, AnalyticsPlugin } = await import('../plugins/index');
+ const registry = new Registry();
+ await registry.register(new AnalyticsPlugin());
+ await expect(registry.register(new AnalyticsPlugin())).rejects.toThrow('already registered');
+ });
});
diff --git a/packages/sdk/src/plugins/registry.ts b/packages/sdk/src/plugins/registry.ts
index cd81da2..7f58408 100644
--- a/packages/sdk/src/plugins/registry.ts
+++ b/packages/sdk/src/plugins/registry.ts
@@ -3,18 +3,19 @@ import type { HookName, PluginRegistry, PluginWithHooks } from './types';
export class Registry implements PluginRegistry {
private plugins: Map = new Map();
- register(plugin: PluginWithHooks): void {
+ async register(plugin: PluginWithHooks): Promise {
if (this.plugins.has(plugin.name)) {
throw new Error(`Plugin "${plugin.name}" is already registered`);
}
+ await plugin.init(this);
this.plugins.set(plugin.name, plugin);
}
- unregister(name: string): void {
+ async unregister(name: string): Promise {
const plugin = this.plugins.get(name);
if (plugin) {
- plugin.destroy().catch(console.error);
this.plugins.delete(name);
+ await plugin.destroy();
}
}
diff --git a/packages/sdk/src/plugins/types.ts b/packages/sdk/src/plugins/types.ts
index 76da472..c87e087 100644
--- a/packages/sdk/src/plugins/types.ts
+++ b/packages/sdk/src/plugins/types.ts
@@ -27,8 +27,8 @@ export interface PluginHooks {
export type PluginWithHooks = Plugin & PluginHooks;
export interface PluginRegistry {
- register(_plugin: PluginWithHooks): void;
- unregister(_name: string): void;
+ register(_plugin: PluginWithHooks): Promise;
+ unregister(_name: string): Promise;
getPlugin(_name: string): PluginWithHooks | undefined;
listPlugins(): PluginWithHooks[];
emit(_hook: HookName, _payload: T): Promise;
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index e01001b..d697420 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -8,6 +8,9 @@ importers:
.:
devDependencies:
+ '@typescript-eslint/parser':
+ specifier: ^8.57.1
+ version: 8.57.1(eslint@8.57.1)(typescript@5.9.3)
eslint:
specifier: ^8.55.0
version: 8.57.1
@@ -2513,6 +2516,38 @@ packages:
- supports-color
dev: true
+ /@typescript-eslint/parser@8.57.1(eslint@8.57.1)(typescript@5.9.3):
+ resolution: {integrity: sha512-k4eNDan0EIMTT/dUKc/g+rsJ6wcHYhNPdY19VoX/EOtaAG8DLtKCykhrUnuHPYvinn5jhAPgD2Qw9hXBwrahsw==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
+ typescript: '>=4.8.4 <6.0.0'
+ dependencies:
+ '@typescript-eslint/scope-manager': 8.57.1
+ '@typescript-eslint/types': 8.57.1
+ '@typescript-eslint/typescript-estree': 8.57.1(typescript@5.9.3)
+ '@typescript-eslint/visitor-keys': 8.57.1
+ debug: 4.4.3(supports-color@8.1.1)
+ eslint: 8.57.1
+ typescript: 5.9.3
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
+ /@typescript-eslint/project-service@8.57.1(typescript@5.9.3):
+ resolution: {integrity: sha512-vx1F37BRO1OftsYlmG9xay1TqnjNVlqALymwWVuYTdo18XuKxtBpCj1QlzNIEHlvlB27osvXFWptYiEWsVdYsg==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ typescript: '>=4.8.4 <6.0.0'
+ dependencies:
+ '@typescript-eslint/tsconfig-utils': 8.57.1(typescript@5.9.3)
+ '@typescript-eslint/types': 8.57.1
+ debug: 4.4.3(supports-color@8.1.1)
+ typescript: 5.9.3
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
/@typescript-eslint/scope-manager@6.21.0:
resolution: {integrity: sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==}
engines: {node: ^16.0.0 || >=18.0.0}
@@ -2521,11 +2556,33 @@ packages:
'@typescript-eslint/visitor-keys': 6.21.0
dev: true
+ /@typescript-eslint/scope-manager@8.57.1:
+ resolution: {integrity: sha512-hs/QcpCwlwT2L5S+3fT6gp0PabyGk4Q0Rv2doJXA0435/OpnSR3VRgvrp8Xdoc3UAYSg9cyUjTeFXZEPg/3OKg==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ dependencies:
+ '@typescript-eslint/types': 8.57.1
+ '@typescript-eslint/visitor-keys': 8.57.1
+ dev: true
+
+ /@typescript-eslint/tsconfig-utils@8.57.1(typescript@5.9.3):
+ resolution: {integrity: sha512-0lgOZB8cl19fHO4eI46YUx2EceQqhgkPSuCGLlGi79L2jwYY1cxeYc1Nae8Aw1xjgW3PKVDLlr3YJ6Bxx8HkWg==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ typescript: '>=4.8.4 <6.0.0'
+ dependencies:
+ typescript: 5.9.3
+ dev: true
+
/@typescript-eslint/types@6.21.0:
resolution: {integrity: sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==}
engines: {node: ^16.0.0 || >=18.0.0}
dev: true
+ /@typescript-eslint/types@8.57.1:
+ resolution: {integrity: sha512-S29BOBPJSFUiblEl6RzPPjJt6w25A6XsBqRVDt53tA/tlL8q7ceQNZHTjPeONt/3S7KRI4quk+yP9jK2WjBiPQ==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ dev: true
+
/@typescript-eslint/typescript-estree@6.21.0(typescript@5.9.3):
resolution: {integrity: sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==}
engines: {node: ^16.0.0 || >=18.0.0}
@@ -2548,6 +2605,26 @@ packages:
- supports-color
dev: true
+ /@typescript-eslint/typescript-estree@8.57.1(typescript@5.9.3):
+ resolution: {integrity: sha512-ybe2hS9G6pXpqGtPli9Gx9quNV0TWLOmh58ADlmZe9DguLq0tiAKVjirSbtM1szG6+QH6rVXyU6GTLQbWnMY+g==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ typescript: '>=4.8.4 <6.0.0'
+ dependencies:
+ '@typescript-eslint/project-service': 8.57.1(typescript@5.9.3)
+ '@typescript-eslint/tsconfig-utils': 8.57.1(typescript@5.9.3)
+ '@typescript-eslint/types': 8.57.1
+ '@typescript-eslint/visitor-keys': 8.57.1
+ debug: 4.4.3(supports-color@8.1.1)
+ minimatch: 10.2.4
+ semver: 7.7.4
+ tinyglobby: 0.2.15
+ ts-api-utils: 2.4.0(typescript@5.9.3)
+ typescript: 5.9.3
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
/@typescript-eslint/visitor-keys@6.21.0:
resolution: {integrity: sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==}
engines: {node: ^16.0.0 || >=18.0.0}
@@ -2556,6 +2633,14 @@ packages:
eslint-visitor-keys: 3.4.3
dev: true
+ /@typescript-eslint/visitor-keys@8.57.1:
+ resolution: {integrity: sha512-YWnmJkXbofiz9KbnbbwuA2rpGkFPLbAIetcCNO6mJ8gdhdZ/v7WDXsoGFAJuM6ikUFKTlSQnjWnVO4ux+UzS6A==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ dependencies:
+ '@typescript-eslint/types': 8.57.1
+ eslint-visitor-keys: 5.0.1
+ dev: true
+
/@ungap/structured-clone@1.3.0:
resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==}
dev: true
@@ -4410,6 +4495,11 @@ packages:
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
dev: true
+ /eslint-visitor-keys@5.0.1:
+ resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==}
+ engines: {node: ^20.19.0 || ^22.13.0 || >=24}
+ dev: true
+
/eslint@8.57.1:
resolution: {integrity: sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@@ -8134,6 +8224,15 @@ packages:
typescript: 5.9.3
dev: true
+ /ts-api-utils@2.4.0(typescript@5.9.3):
+ resolution: {integrity: sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==}
+ engines: {node: '>=18.12'}
+ peerDependencies:
+ typescript: '>=4.8.4'
+ dependencies:
+ typescript: 5.9.3
+ dev: true
+
/ts-command-line-args@2.5.1:
resolution: {integrity: sha512-H69ZwTw3rFHb5WYpQya40YAX2/w7Ut75uUECbgBIsLmM+BNuYnxsltfyyLMxy6sEeKxgijLTnQtLd0nKd6+IYw==}
hasBin: true
From 86ce353037c910b56095a92a024c8d096df5a045 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 18 Mar 2026 10:11:56 +0000
Subject: [PATCH 7/8] Fix web test CI failure: switch vitest env from jsdom to
node (ESM/CJS crash on Node 18)
Co-authored-by: SMSDAO <144380926+SMSDAO@users.noreply.github.com>
---
apps/web/vitest.config.ts | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/apps/web/vitest.config.ts b/apps/web/vitest.config.ts
index d3659d7..0cc9ae8 100644
--- a/apps/web/vitest.config.ts
+++ b/apps/web/vitest.config.ts
@@ -5,9 +5,8 @@ import path from 'path';
export default defineConfig({
plugins: [react()],
test: {
- environment: 'jsdom',
+ environment: 'node',
globals: true,
- setupFiles: ['./vitest.setup.ts'],
},
resolve: {
alias: {
From 1563b7f2d7a1e4b9374d0744a8b0b7cc6142baaa Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 18 Mar 2026 10:23:03 +0000
Subject: [PATCH 8/8] Apply review 2 feedback: fix routeTask, remove unused
SwarmConfig.agents + Plugin, add PWA icons
Co-authored-by: SMSDAO <144380926+SMSDAO@users.noreply.github.com>
---
apps/web/public/icons/icon-192.png | Bin 0 -> 548 bytes
apps/web/public/icons/icon-512.png | Bin 0 -> 1882 bytes
packages/agents/src/__tests__/swarm.test.ts | 14 +++++++++++++-
packages/agents/src/swarm.ts | 4 +---
packages/agents/src/types.ts | 8 --------
5 files changed, 14 insertions(+), 12 deletions(-)
create mode 100644 apps/web/public/icons/icon-192.png
create mode 100644 apps/web/public/icons/icon-512.png
diff --git a/apps/web/public/icons/icon-192.png b/apps/web/public/icons/icon-192.png
new file mode 100644
index 0000000000000000000000000000000000000000..8156f04eb0b90c6eed38c9dd1ea65697e4f985c3
GIT binary patch
literal 548
zcmeAS@N?(olHy`uVBq!ia0vp^2SAvE2}s`E_d9@rf$_Dci(^Q|oVS-8IT;if4mfPS
zvA@^$Oal9Nfrk&6-fT3y-j@Bik}=*`ptI3~$0bQYhk-8
literal 0
HcmV?d00001
diff --git a/packages/agents/src/__tests__/swarm.test.ts b/packages/agents/src/__tests__/swarm.test.ts
index 8301c38..7867edb 100644
--- a/packages/agents/src/__tests__/swarm.test.ts
+++ b/packages/agents/src/__tests__/swarm.test.ts
@@ -20,7 +20,7 @@ describe('Swarm orchestrator', () => {
let monitoringAgent: MonitoringAgent;
beforeEach(() => {
- swarm = new Swarm({ name: 'test-swarm', agents: [] });
+ swarm = new Swarm({ name: 'test-swarm' });
pricingAgent = new PricingAgent({ id: 'price-1', name: 'Pricing' });
monitoringAgent = new MonitoringAgent({ id: 'monitor-1', name: 'Monitor' });
});
@@ -70,6 +70,18 @@ describe('Swarm orchestrator', () => {
expect(result.error).toContain('No agents available');
});
+ it('returns an error result when agents are registered but none are idle', async () => {
+ swarm.registerAgent(pricingAgent);
+ await swarm.start();
+ // Force the agent into a non-idle state
+ (pricingAgent as unknown as { status: string }).status = 'running';
+ const task = makeTask('price', {});
+ const result = await swarm.dispatch(task);
+ expect(result.success).toBe(false);
+ expect(result.error).toContain('No agents available');
+ await swarm.stop();
+ });
+
it('processes a queue of tasks', async () => {
swarm.registerAgent(pricingAgent);
await swarm.start();
diff --git a/packages/agents/src/swarm.ts b/packages/agents/src/swarm.ts
index 8d70eb4..bde3b4c 100644
--- a/packages/agents/src/swarm.ts
+++ b/packages/agents/src/swarm.ts
@@ -106,8 +106,6 @@ export class Swarm {
private routeTask(_task: Task): Agent | undefined {
const agentsList = Array.from(this.agents.values());
- const available = agentsList.filter((a) => a.getStatus() === 'idle');
- if (available.length === 0) return agentsList[0];
- return available[0];
+ return agentsList.find((a) => a.getStatus() === 'idle');
}
}
diff --git a/packages/agents/src/types.ts b/packages/agents/src/types.ts
index 04210f1..9ca728c 100644
--- a/packages/agents/src/types.ts
+++ b/packages/agents/src/types.ts
@@ -34,14 +34,6 @@ export interface TaskResult {
export interface SwarmConfig {
name: string;
- agents: AgentConfig[];
maxConcurrency?: number;
messageBusSize?: number;
}
-
-export interface Plugin {
- name: string;
- version: string;
- init(_registry: unknown): Promise;
- destroy(): Promise;
-}