Built-in design generation now runs as an agentic MCP tool-loop (reusing the agent-rs BuiltInProvider), gated behind OPENPENCIL_DESIGN_AGENT_LOOP / the Settings experimental toggle; the orchestrator stays the default. - design-agent system prompt + in-process design toolset (parity-locked with the MCP surface) + flag-gated Intent::Design routing - spawn_agents execution as sequential sub-loops + live creation-mode badges (per-agent glow + 'N/M designing...' header) - new MCP tools: get_guidelines, ToolSearch, get_screenshot, get_editor_state, export_nodes, spawn_agents; style-guide local audit - #27 AI panel restyle: rounded tool cards + green check-rings, gray user bubbles, model-pill bottom toolbar, header, empty-state pills, the PARALLEL AGENTS (agent_team_size) 1x-6x chip dropdown - multi-chat tabs: ChatSessions model (Deref-to-active) + tab row UI (switch / close / + / Cmd+T) with each run bound to its tab Large checkpoint commit spanning the working tree (Rust shell crates).
83 lines
2.9 KiB
TypeScript
83 lines
2.9 KiB
TypeScript
import { describe, it, expect, beforeEach } from 'vitest';
|
|
import {
|
|
registerSSEClient,
|
|
unregisterSSEClient,
|
|
setSyncDocument,
|
|
setSyncSelection,
|
|
markClientActive,
|
|
getLastActiveClientId,
|
|
isClientConnected,
|
|
sendToClient,
|
|
clearSyncState,
|
|
} from '../utils/mcp-sync-state';
|
|
import type { PenDocument } from '../../src/types/pen';
|
|
|
|
describe('mcp-sync-state: active client tracking', () => {
|
|
beforeEach(() => {
|
|
clearSyncState();
|
|
});
|
|
|
|
it('getLastActiveClientId returns null initially', () => {
|
|
expect(getLastActiveClientId()).toBeNull();
|
|
});
|
|
|
|
it('setSyncDocument updates lastActiveClientId when sourceClientId provided', () => {
|
|
registerSSEClient('client-a', { push: () => {} });
|
|
setSyncDocument({ version: '1.0.0', children: [] } as PenDocument, 'client-a');
|
|
expect(getLastActiveClientId()).toBe('client-a');
|
|
});
|
|
|
|
it('setSyncSelection updates lastActiveClientId', () => {
|
|
registerSSEClient('client-b', { push: () => {} });
|
|
setSyncSelection(['node-1'], 'page-1', 'client-b');
|
|
expect(getLastActiveClientId()).toBe('client-b');
|
|
});
|
|
|
|
it('markClientActive updates lastActiveClientId only for connected clients', () => {
|
|
registerSSEClient('client-c', { push: () => {} });
|
|
markClientActive('client-c');
|
|
expect(getLastActiveClientId()).toBe('client-c');
|
|
markClientActive('client-nonexistent');
|
|
expect(getLastActiveClientId()).toBe('client-c');
|
|
});
|
|
|
|
it('isClientConnected reflects registration state', () => {
|
|
registerSSEClient('client-d', { push: () => {} });
|
|
expect(isClientConnected('client-d')).toBe(true);
|
|
unregisterSSEClient('client-d');
|
|
expect(isClientConnected('client-d')).toBe(false);
|
|
});
|
|
|
|
it('sendToClient dispatches payload to a specific client', () => {
|
|
const received: string[] = [];
|
|
registerSSEClient('client-e', { push: (data: string) => received.push(data) });
|
|
const ok = sendToClient('client-e', { type: 'screenshot:request' });
|
|
expect(ok).toBe(true);
|
|
expect(received).toHaveLength(1);
|
|
expect(JSON.parse(received[0]).type).toBe('screenshot:request');
|
|
});
|
|
|
|
it('sendToClient returns false for unknown client', () => {
|
|
expect(sendToClient('nope', { type: 'x' })).toBe(false);
|
|
});
|
|
|
|
it('broadcasts document updates to other clients and prunes broken writers', () => {
|
|
const received: string[] = [];
|
|
registerSSEClient('client-source', { push: () => {} });
|
|
registerSSEClient('client-target', { push: (data: string) => received.push(data) });
|
|
registerSSEClient('client-broken', {
|
|
push: () => {
|
|
throw new Error('writer closed');
|
|
},
|
|
});
|
|
|
|
setSyncDocument({ version: '1.0.0', children: [] } as PenDocument, 'client-source');
|
|
|
|
expect(received).toHaveLength(1);
|
|
expect(JSON.parse(received[0]).type).toBe('document:update');
|
|
expect(isClientConnected('client-source')).toBe(true);
|
|
expect(isClientConnected('client-target')).toBe(true);
|
|
expect(isClientConnected('client-broken')).toBe(false);
|
|
});
|
|
});
|