mirror of
https://github.com/ruvnet/ruflo.git
synced 2026-11-04 19:17:02 +09:00
* fix(wasm): bump default Sonnet model from 20250514 to 4.6 (#1810) `wasm_agent_create` (gallery + plain) hardcoded `anthropic:claude-sonnet-4-20250514` as the default model. New WASM agents silently inherited a year-old Sonnet ID unless the caller passed an explicit `model`, which is a quiet UX/pricing failure that new users won't catch until they hit version-specific behaviour. Bump to `anthropic:claude-sonnet-4-6` (current Sonnet at time of writing) in all four locations the previous ID was hardcoded: - `src/ruvector/agent-wasm.ts` — actual default (this is what actually shipped to users) - `src/commands/agent-wasm.ts` — `--model` flag description - `src/commands/agent-wasm.ts` — example command - `src/mcp-tools/wasm-agent-tools.ts` — MCP `wasm_agent_create` schema description - `__tests__/ruvector/agent-wasm.test.ts` — assertion in "creates agent with defaults" A future follow-up should replace the hardcode with a config-driven default (e.g. read from `claudeFlow.modelPreferences.default`) so Anthropic version cuts don't require a code change. Tracked separately. Closes #1810. Co-Authored-By: RuFlo <ruv@ruv.net> * fix(wasm,plugins): pin gallery test + sweep remaining stale model IDs (#1810) Follow-up to the WASM default bump in this same PR. Reviewing the issue more carefully, the reporter's repro path goes through `createAgentFromTemplate('researcher')` (the GALLERY path) — and while that path *does* inherit the new default (it passes `model: undefined`, falling through to `createWasmAgent`'s default), nothing in the test suite actually pinned the gallery output to the new default. A future regression would only surface in `creates agent with defaults` (the bare-call test), not in the gallery test the user actually hits. __tests__/ruvector/agent-wasm.test.ts:326 + expect(info.model).toBe('anthropic:claude-sonnet-4-6'); While I had grep open, also swept the rest of the workspace for the `claude-sonnet-4-20250514` literal and the matching old Claude 4.x model IDs: - plugins/examples/plugin-creator/index.ts (×2 — runtime + code-gen template string) - plugins/examples/plugin-creator/plugin-creator.test.ts (assertion) - plugins/src/collections/official/index.ts (×4 — coder, tester, reviewer, coordinator agent defs) - plugins/src/providers/index.ts (Claude provider's models[] list — also bumped opus-4-5 → opus-4-7 and 3-5-haiku → haiku-4-5) - plugins/README.md (example snippet) - mcp/.claude/settings.json (modelPreferences default + routing) Historical ADR docs (`v3/implementation/adrs/`) still reference the old IDs by design — they capture decisions at issuance time. Comments in the WASM agent source that reference the old hardcode for context are kept too. Strengthens the original closes-#1810 fix from "default works" to "default works AND a regression of the gallery path is caught by tests AND no other surface in the workspace ships the stale ID anymore." Co-Authored-By: RuFlo <ruv@ruv.net>
313 lines
11 KiB
TypeScript
313 lines
11 KiB
TypeScript
/**
|
|
* Plugin Creator Tests
|
|
*/
|
|
|
|
import { describe, it, expect } from 'vitest';
|
|
import {
|
|
pluginCreatorPlugin,
|
|
generatePlugin,
|
|
generateToolCode,
|
|
generateHookCode,
|
|
generateWorkerCode,
|
|
generateAgentTypeCode,
|
|
PLUGIN_TEMPLATES,
|
|
type CreatePluginOptions,
|
|
} from './index.js';
|
|
import { HookEvent } from '../../src/types/index.js';
|
|
|
|
describe('Plugin Creator Plugin', () => {
|
|
describe('pluginCreatorPlugin', () => {
|
|
it('should have correct metadata', () => {
|
|
expect(pluginCreatorPlugin.metadata.name).toBe('plugin-creator');
|
|
expect(pluginCreatorPlugin.metadata.version).toBe('1.0.0');
|
|
expect(pluginCreatorPlugin.metadata.tags).toContain('meta');
|
|
});
|
|
|
|
it('should register MCP tools', () => {
|
|
const tools = pluginCreatorPlugin.registerMCPTools?.() ?? [];
|
|
expect(tools.length).toBeGreaterThan(0);
|
|
|
|
const toolNames = tools.map(t => t.name);
|
|
expect(toolNames).toContain('create-plugin');
|
|
expect(toolNames).toContain('list-plugin-templates');
|
|
expect(toolNames).toContain('generate-tool');
|
|
expect(toolNames).toContain('generate-hook');
|
|
expect(toolNames).toContain('generate-worker');
|
|
});
|
|
|
|
it('should register hooks', () => {
|
|
const hooks = pluginCreatorPlugin.registerHooks?.() ?? [];
|
|
expect(hooks.length).toBeGreaterThan(0);
|
|
expect(hooks[0].name).toBe('plugin-creator-logger');
|
|
});
|
|
|
|
it('should register workers', () => {
|
|
const workers = pluginCreatorPlugin.registerWorkers?.() ?? [];
|
|
expect(workers.length).toBeGreaterThan(0);
|
|
expect(workers[0].name).toBe('plugin-code-generator');
|
|
});
|
|
});
|
|
|
|
describe('PLUGIN_TEMPLATES', () => {
|
|
it('should have all expected templates', () => {
|
|
const templateNames = Object.keys(PLUGIN_TEMPLATES);
|
|
expect(templateNames).toContain('minimal');
|
|
expect(templateNames).toContain('tool-plugin');
|
|
expect(templateNames).toContain('hooks-plugin');
|
|
expect(templateNames).toContain('worker-plugin');
|
|
expect(templateNames).toContain('swarm-plugin');
|
|
expect(templateNames).toContain('full-featured');
|
|
expect(templateNames).toContain('security-focused');
|
|
});
|
|
|
|
it('should have valid template structures', () => {
|
|
for (const [name, template] of Object.entries(PLUGIN_TEMPLATES)) {
|
|
expect(template.name).toBeDefined();
|
|
expect(template.description).toBeDefined();
|
|
expect(template.category).toMatch(/^(tools|hooks|workers|swarm|full)$/);
|
|
expect(Array.isArray(template.features)).toBe(true);
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('generatePlugin', () => {
|
|
it('should generate a minimal plugin', () => {
|
|
const result = generatePlugin({
|
|
name: 'test-plugin',
|
|
version: '1.0.0',
|
|
});
|
|
|
|
expect(result.plugin).toBeDefined();
|
|
expect(result.metadata.name).toBe('test-plugin');
|
|
expect(result.metadata.version).toBe('1.0.0');
|
|
expect(result.code).toContain("PluginBuilder('test-plugin'");
|
|
});
|
|
|
|
it('should generate a plugin with tools', () => {
|
|
const result = generatePlugin({
|
|
name: 'tools-test',
|
|
features: { tools: true },
|
|
toolNames: ['my-tool', 'another-tool'],
|
|
});
|
|
|
|
const tools = result.plugin.registerMCPTools?.() ?? [];
|
|
expect(tools.length).toBe(2);
|
|
expect(tools[0].name).toBe('my-tool');
|
|
expect(tools[1].name).toBe('another-tool');
|
|
expect(result.code).toContain('MCPToolBuilder');
|
|
});
|
|
|
|
it('should generate a plugin with hooks', () => {
|
|
const result = generatePlugin({
|
|
name: 'hooks-test',
|
|
features: { hooks: true },
|
|
hookEvents: [HookEvent.SessionStart, HookEvent.PostTaskComplete],
|
|
});
|
|
|
|
const hooks = result.plugin.registerHooks?.() ?? [];
|
|
expect(hooks.length).toBe(2);
|
|
expect(result.code).toContain('HookBuilder');
|
|
});
|
|
|
|
it('should generate a plugin with workers', () => {
|
|
const result = generatePlugin({
|
|
name: 'workers-test',
|
|
features: { workers: true },
|
|
workerTypes: ['coder', 'reviewer'],
|
|
});
|
|
|
|
const workers = result.plugin.registerWorkers?.() ?? [];
|
|
expect(workers.length).toBe(2);
|
|
expect(result.code).toContain('WorkerFactory');
|
|
});
|
|
|
|
it('should generate a plugin with swarm capabilities', () => {
|
|
const result = generatePlugin({
|
|
name: 'swarm-test',
|
|
features: { swarm: true },
|
|
agentTypes: ['coordinator', 'worker-agent'],
|
|
});
|
|
|
|
const agents = result.plugin.registerAgentTypes?.() ?? [];
|
|
expect(agents.length).toBe(2);
|
|
expect(result.code).toContain('AgentTypeDefinition');
|
|
});
|
|
|
|
it('should apply template features', () => {
|
|
const result = generatePlugin({
|
|
name: 'full-test',
|
|
template: 'full-featured',
|
|
});
|
|
|
|
const tools = result.plugin.registerMCPTools?.() ?? [];
|
|
const hooks = result.plugin.registerHooks?.() ?? [];
|
|
const workers = result.plugin.registerWorkers?.() ?? [];
|
|
const agents = result.plugin.registerAgentTypes?.() ?? [];
|
|
|
|
expect(tools.length).toBeGreaterThan(0);
|
|
expect(hooks.length).toBeGreaterThan(0);
|
|
expect(workers.length).toBeGreaterThan(0);
|
|
expect(agents.length).toBeGreaterThan(0);
|
|
});
|
|
|
|
it('should reject invalid plugin names', () => {
|
|
expect(() => generatePlugin({ name: 'Invalid Name' })).toThrow();
|
|
expect(() => generatePlugin({ name: '123-start' })).toThrow();
|
|
expect(() => generatePlugin({ name: 'ab' })).toThrow(); // Too short
|
|
});
|
|
});
|
|
|
|
describe('generateToolCode', () => {
|
|
it('should generate valid tool definition', () => {
|
|
const { definition, code } = generateToolCode('my-tool');
|
|
|
|
expect(definition.name).toBe('my-tool');
|
|
expect(definition.handler).toBeDefined();
|
|
expect(code).toContain('MCPToolBuilder');
|
|
expect(code).toContain('my-tool');
|
|
});
|
|
|
|
it('should sanitize invalid tool names', () => {
|
|
const { definition } = generateToolCode('Invalid Tool Name!');
|
|
expect(definition.name).toBe('custom-tool');
|
|
});
|
|
|
|
it('should create working handler', async () => {
|
|
const { definition } = generateToolCode('test-tool');
|
|
const result = await definition.handler({ input: 'test' });
|
|
|
|
expect(result.content).toBeDefined();
|
|
expect(result.content[0].type).toBe('text');
|
|
expect(result.content[0].text).toContain('test-tool');
|
|
});
|
|
});
|
|
|
|
describe('generateHookCode', () => {
|
|
it('should generate valid hook definition', () => {
|
|
const { definition, code } = generateHookCode(HookEvent.SessionStart);
|
|
|
|
expect(definition.event).toBe(HookEvent.SessionStart);
|
|
expect(definition.handler).toBeDefined();
|
|
expect(code).toContain('HookBuilder');
|
|
expect(code).toContain('SessionStart');
|
|
});
|
|
|
|
it('should create working handler', async () => {
|
|
const { definition } = generateHookCode(HookEvent.PostTaskComplete);
|
|
const result = await definition.handler({
|
|
event: HookEvent.PostTaskComplete,
|
|
data: { taskId: '123' },
|
|
timestamp: new Date(),
|
|
});
|
|
|
|
expect(result.success).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('generateWorkerCode', () => {
|
|
it('should generate valid worker definitions for known types', () => {
|
|
const types = ['coder', 'reviewer', 'tester', 'researcher', 'planner', 'coordinator', 'security', 'performance'];
|
|
|
|
for (const type of types) {
|
|
const { definition, code } = generateWorkerCode(type);
|
|
|
|
expect(definition.type).toBe(type);
|
|
expect(definition.name).toBe(`${type}-worker`);
|
|
expect(definition.capabilities.length).toBeGreaterThan(0);
|
|
expect(code).toContain('WorkerFactory');
|
|
}
|
|
});
|
|
|
|
it('should fallback to specialized for unknown types', () => {
|
|
const { definition, code } = generateWorkerCode('custom-type');
|
|
|
|
expect(definition.type).toBe('specialized');
|
|
expect(code).toContain('createSpecialized');
|
|
});
|
|
});
|
|
|
|
describe('generateAgentTypeCode', () => {
|
|
it('should generate valid agent type definition', () => {
|
|
const { definition, code } = generateAgentTypeCode('coordinator');
|
|
|
|
expect(definition.type).toBe('coordinator');
|
|
expect(definition.name).toBe('coordinator Agent');
|
|
expect(definition.model).toBe('claude-sonnet-4-6');
|
|
expect(code).toContain('AgentTypeDefinition');
|
|
});
|
|
|
|
it('should sanitize invalid agent type names', () => {
|
|
const { definition } = generateAgentTypeCode('Invalid Agent!');
|
|
expect(definition.type).toBe('custom-agent');
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('MCP Tool Handlers', () => {
|
|
it('create-plugin tool should work', async () => {
|
|
const tools = pluginCreatorPlugin.registerMCPTools?.() ?? [];
|
|
const createTool = tools.find(t => t.name === 'create-plugin');
|
|
|
|
expect(createTool).toBeDefined();
|
|
|
|
const result = await createTool!.handler({
|
|
name: 'test-created-plugin',
|
|
version: '1.0.0',
|
|
template: 'tool-plugin',
|
|
});
|
|
|
|
expect(result.content[0].text).toContain('test-created-plugin');
|
|
expect(result.content[0].text).toContain('created successfully');
|
|
});
|
|
|
|
it('list-plugin-templates tool should work', async () => {
|
|
const tools = pluginCreatorPlugin.registerMCPTools?.() ?? [];
|
|
const listTool = tools.find(t => t.name === 'list-plugin-templates');
|
|
|
|
expect(listTool).toBeDefined();
|
|
|
|
const result = await listTool!.handler({});
|
|
|
|
expect(result.content[0].text).toContain('Available Plugin Templates');
|
|
expect(result.content[0].text).toContain('minimal');
|
|
expect(result.content[0].text).toContain('full-featured');
|
|
});
|
|
|
|
it('generate-tool tool should work', async () => {
|
|
const tools = pluginCreatorPlugin.registerMCPTools?.() ?? [];
|
|
const genTool = tools.find(t => t.name === 'generate-tool');
|
|
|
|
expect(genTool).toBeDefined();
|
|
|
|
const result = await genTool!.handler({ name: 'my-custom-tool' });
|
|
|
|
expect(result.content[0].text).toContain('Generated Tool');
|
|
expect(result.content[0].text).toContain('my-custom-tool');
|
|
expect(result.content[0].text).toContain('MCPToolBuilder');
|
|
});
|
|
|
|
it('generate-hook tool should work', async () => {
|
|
const tools = pluginCreatorPlugin.registerMCPTools?.() ?? [];
|
|
const genHook = tools.find(t => t.name === 'generate-hook');
|
|
|
|
expect(genHook).toBeDefined();
|
|
|
|
const result = await genHook!.handler({ event: HookEvent.SessionStart });
|
|
|
|
expect(result.content[0].text).toContain('Generated Hook');
|
|
expect(result.content[0].text).toContain('HookBuilder');
|
|
});
|
|
|
|
it('generate-worker tool should work', async () => {
|
|
const tools = pluginCreatorPlugin.registerMCPTools?.() ?? [];
|
|
const genWorker = tools.find(t => t.name === 'generate-worker');
|
|
|
|
expect(genWorker).toBeDefined();
|
|
|
|
const result = await genWorker!.handler({ type: 'coder' });
|
|
|
|
expect(result.content[0].text).toContain('Generated Worker');
|
|
expect(result.content[0].text).toContain('WorkerFactory');
|
|
});
|
|
});
|