chart data loading

This commit is contained in:
2026-03-24 21:37:49 -04:00
parent f6bd22a8ef
commit c76887ab92
65 changed files with 6350 additions and 713 deletions

View File

@@ -0,0 +1,190 @@
/**
* Container Sync
*
* Handles synchronization of persistent workspace stores with the user container
* via MCP tools. Persistent stores (chartStore, userPreferences, etc.) are
* stored in the container and loaded/saved via MCP tool calls.
*
* Container-side storage: /data/workspace/{store_name}.json
*
* MCP Tools used:
* - workspace_read(store_name) -> dict
* - workspace_write(store_name, data) -> None
* - workspace_patch(store_name, patch) -> dict (new state)
*/
import type { FastifyBaseLogger } from 'fastify';
import type { Operation as JsonPatchOp } from 'fast-json-patch';
import type { MCPClientConnector } from '../harness/mcp-client.js';
/**
* Result of loading a store from the container.
*/
export interface LoadResult {
exists: boolean;
state?: unknown;
error?: string;
}
/**
* Result of saving a store to the container.
*/
export interface SaveResult {
success: boolean;
error?: string;
}
/**
* Result of patching a store in the container.
*/
export interface PatchResult {
success: boolean;
newState?: unknown;
error?: string;
}
/**
* Handles synchronization with the user's container via MCP.
*/
export class ContainerSync {
private mcpClient: MCPClientConnector;
private logger: FastifyBaseLogger;
constructor(mcpClient: MCPClientConnector, logger: FastifyBaseLogger) {
this.mcpClient = mcpClient;
this.logger = logger.child({ component: 'ContainerSync' });
}
/**
* Load a workspace store from the container.
* Returns the stored state or indicates the store doesn't exist.
*/
async loadStore(storeName: string): Promise<LoadResult> {
if (!this.mcpClient.isConnected()) {
this.logger.warn({ store: storeName }, 'MCP client not connected, cannot load store');
return { exists: false, error: 'MCP client not connected' };
}
try {
this.logger.debug({ store: storeName }, 'Loading store from container');
const result = (await this.mcpClient.callTool('workspace_read', {
store_name: storeName,
})) as { exists: boolean; data?: unknown; error?: string };
if (result.error) {
this.logger.warn({ store: storeName, error: result.error }, 'Container returned error');
return { exists: false, error: result.error };
}
if (!result.exists) {
this.logger.debug({ store: storeName }, 'Store does not exist in container');
return { exists: false };
}
this.logger.debug({ store: storeName }, 'Loaded store from container');
return { exists: true, state: result.data };
} catch (error) {
const message = error instanceof Error ? error.message : 'Unknown error';
this.logger.error({ store: storeName, error: message }, 'Failed to load store from container');
return { exists: false, error: message };
}
}
/**
* Save a workspace store to the container.
* Overwrites any existing state.
*/
async saveStore(storeName: string, state: unknown): Promise<SaveResult> {
if (!this.mcpClient.isConnected()) {
this.logger.warn({ store: storeName }, 'MCP client not connected, cannot save store');
return { success: false, error: 'MCP client not connected' };
}
try {
this.logger.debug({ store: storeName }, 'Saving store to container');
const result = (await this.mcpClient.callTool('workspace_write', {
store_name: storeName,
data: state,
})) as { success: boolean; error?: string };
if (result.error || !result.success) {
this.logger.warn({ store: storeName, error: result.error }, 'Failed to save store');
return { success: false, error: result.error || 'Unknown error' };
}
this.logger.debug({ store: storeName }, 'Saved store to container');
return { success: true };
} catch (error) {
const message = error instanceof Error ? error.message : 'Unknown error';
this.logger.error({ store: storeName, error: message }, 'Failed to save store to container');
return { success: false, error: message };
}
}
/**
* Apply a JSON patch to a store in the container.
* Returns the new state after applying the patch.
*/
async patchStore(storeName: string, patch: JsonPatchOp[]): Promise<PatchResult> {
if (!this.mcpClient.isConnected()) {
this.logger.warn({ store: storeName }, 'MCP client not connected, cannot patch store');
return { success: false, error: 'MCP client not connected' };
}
try {
this.logger.debug({ store: storeName, patchOps: patch.length }, 'Patching store in container');
const result = (await this.mcpClient.callTool('workspace_patch', {
store_name: storeName,
patch,
})) as { success: boolean; data?: unknown; error?: string };
if (result.error || !result.success) {
this.logger.warn({ store: storeName, error: result.error }, 'Failed to patch store');
return { success: false, error: result.error || 'Unknown error' };
}
this.logger.debug({ store: storeName }, 'Patched store in container');
return { success: true, newState: result.data };
} catch (error) {
const message = error instanceof Error ? error.message : 'Unknown error';
this.logger.error({ store: storeName, error: message }, 'Failed to patch store in container');
return { success: false, error: message };
}
}
/**
* Load all persistent stores from the container.
* Returns a map of store name -> state.
*/
async loadAllStores(storeNames: string[]): Promise<Map<string, unknown>> {
const states = new Map<string, unknown>();
for (const storeName of storeNames) {
const result = await this.loadStore(storeName);
if (result.exists && result.state !== undefined) {
states.set(storeName, result.state);
}
}
return states;
}
/**
* Save all persistent stores to the container.
*/
async saveAllStores(stores: Map<string, unknown>): Promise<void> {
for (const [storeName, state] of stores) {
await this.saveStore(storeName, state);
}
}
/**
* Check if MCP client is connected.
*/
isConnected(): boolean {
return this.mcpClient.isConnected();
}
}