@documentation-robotics/cli 0.1.11 → 0.1.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/cli.js +1 -1
- package/dist/commands/version.js +1 -1
- package/dist/core/graph-model.js +14 -1
- package/dist/core/graph-model.js.map +2 -2
- package/dist/core/model.js +1 -1
- package/dist/core/model.js.map +2 -2
- package/dist/core/reference-registry.js +11 -5
- package/dist/core/reference-registry.js.map +2 -2
- package/dist/export/plantuml-exporter.js +3 -1
- package/dist/export/plantuml-exporter.js.map +2 -2
- package/dist/server/server.js +25 -6
- package/dist/server/server.js.map +2 -2
- package/dist/telemetry/index.js +1 -1
- package/dist/utils/element-reference-shape.js +12 -0
- package/dist/utils/element-reference-shape.js.map +7 -0
- package/dist/utils/spec-version.js +1 -1
- package/dist/utils/spec-version.js.map +1 -1
- package/dist/viewer/README.md +6 -6
- package/dist/viewer/assets/main-B7AOQXXX.js +81 -0
- package/dist/viewer/assets/main-vdrFrG7q.css +1 -0
- package/dist/viewer/index.html +2 -2
- package/dist/viewer/manifest.json +10 -10
- package/dist/viewer/version.json +1 -1
- package/package.json +1 -1
- package/dist/viewer/assets/main-BRPZyc36.css +0 -1
- package/dist/viewer/assets/main-BrpLargo.js +0 -81
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/server/server.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Visualization Server\n * HTTP server with WebSocket support for real-time model updates\n */\n\nimport { OpenAPIHono, createRoute, z } from \"@hono/zod-openapi\";\nimport { cors } from \"hono/cors\";\nimport { swaggerUI } from \"@hono/swagger-ui\";\nimport { serve } from \"@hono/node-server\";\nimport { createNodeWebSocket } from \"@hono/node-ws\";\nimport { spawn, spawnSync, type ChildProcess } from \"child_process\";\nimport chokidar from \"chokidar\";\nimport { randomBytes } from \"crypto\";\nimport { createRequire } from \"module\";\nimport { fileURLToPath } from \"url\";\nimport path from \"path\";\n\nconst require = createRequire(import.meta.url);\nimport { Model } from \"../core/model.js\";\nimport { Element } from \"../core/element.js\";\nimport { Changeset as StagedChangeset } from \"../core/changeset.js\";\nimport { StagedChangesetStorage } from \"../core/staged-changeset-storage.js\";\nimport { CANONICAL_LAYER_NAMES } from \"../core/layers.js\";\nimport { telemetryMiddleware } from \"./telemetry-middleware.js\";\nimport { BaseChatClient } from \"../coding-agents/base-chat-client.js\";\nimport { ClaudeCodeClient } from \"../coding-agents/claude-code-client.js\";\nimport { CopilotClient } from \"../coding-agents/copilot-client.js\";\nimport { detectAvailableClients, selectChatClient } from \"../coding-agents/chat-utils.js\";\nimport { getErrorMessage } from \"../utils/errors.js\";\nimport {\n AnnotationCreateSchema,\n AnnotationUpdateSchema,\n AnnotationReplyCreateSchema,\n LayerNameSchema,\n IdSchema,\n AnnotationFilterSchema,\n ErrorResponseSchema,\n HealthResponseSchema,\n AnnotationSchema,\n AnnotationReplySchema,\n AnnotationsListSchema,\n ChangesetsListSchema,\n ChangesetDetailSchema,\n ModelResponseSchema,\n LayerResponseSchema,\n SpecResponseSchema,\n AnnotationRepliesSchema,\n WSMessageSchema,\n SimpleWSMessageSchema,\n JSONRPCRequestSchema,\n JSONRPCResponseSchema,\n} from \"./schemas.js\";\n\n/**\n * JSON-RPC 2.0 Error Codes\n * Subset of JSON-RPC 2.0 error codes used in this implementation.\n * Standard JSON-RPC 2.0 also defines PARSE_ERROR (-32700) and INVALID_REQUEST (-32600)\n * for protocol-level errors not applicable to WebSocket message handling.\n */\nconst JSONRPC_ERRORS = {\n // Standard JSON-RPC errors\n INVALID_PARAMS: -32602, // Invalid method parameters\n METHOD_NOT_FOUND: -32601, // Method does not exist\n INTERNAL_ERROR: -32603, // Internal server error\n\n // Custom application errors\n NO_CLIENT_AVAILABLE: -32001, // No chat client available\n} as const;\n\n/**\n * Type Casting Note\n *\n * Type casts (`as any`) are used in this file due to @hono/zod-openapi v1.2.1\n * type inference limitations with async middleware handlers. The middleware\n * properly validates and transforms request/response data at runtime via Zod,\n * so the type assertions are safe despite bypassing TypeScript's type checker.\n *\n * TODO: Remove these casts after @hono/zod-openapi improves async handler typing\n * Upstream tracking: https://github.com/honojs/hono/issues/3524\n * Target version: @hono/zod-openapi v2.0.0+ (when async handler typing is improved)\n *\n * Affected endpoints:\n * - Annotation creation (POST /api/annotations)\n * - Annotation retrieval (GET /api/annotations/:id)\n * - Annotation updates (PATCH /api/annotations/:id)\n * - Annotation replacement (PUT /api/annotations/:id)\n * - Annotation deletion (DELETE /api/annotations/:id)\n * - Layer retrieval (GET /api/layers/:layerName)\n * - Schema retrieval (GET /api/spec)\n */\n\n// WebSocket message types derived from Zod schemas for type safety and runtime validation\ntype SimpleWSMessage = z.infer<typeof SimpleWSMessageSchema>;\ntype JSONRPCRequest = z.infer<typeof JSONRPCRequestSchema>;\ntype JSONRPCResponse = z.infer<typeof JSONRPCResponseSchema>;\ntype WSMessage = z.infer<typeof WSMessageSchema>;\n\ntype AnnotationReply = z.infer<typeof AnnotationReplySchema>;\n\n// Derive ClientAnnotation type from AnnotationSchema with proper serialization\ntype ClientAnnotation = z.infer<typeof AnnotationSchema>;\n\n// Type for WebSocket context from Hono's WebSocket helper (hono/ws), shared by all\n// runtime adapters (@hono/node-ws, hono/bun, hono/cloudflare-workers, etc.)\n// Defines the interface for WebSocket operations within the Hono environment\ninterface HonoWSContext {\n send(source: string | ArrayBuffer | Uint8Array | ArrayBufferView, options?: { compress?: boolean }): void;\n close(code?: number, reason?: string): void;\n}\n\nexport interface VisualizationServerOptions {\n authEnabled?: boolean;\n authToken?: string;\n withDanger?: boolean;\n viewerPath?: string;\n}\n\n/**\n * Visualization Server class\n */\nexport class VisualizationServer {\n private app: OpenAPIHono;\n private model: Model;\n private _server?: ReturnType<typeof serve>; // Node HTTP server returned by @hono/node-server\n private upgradeWebSocket?: ReturnType<typeof createNodeWebSocket>[\"upgradeWebSocket\"];\n private injectWebSocket?: ReturnType<typeof createNodeWebSocket>[\"injectWebSocket\"];\n private clients: Set<HonoWSContext> = new Set();\n private watcher?: any;\n private annotations: Map<string, ClientAnnotation> = new Map(); // annotationId -> annotation\n private annotationsByElement: Map<string, Set<string>> = new Map(); // elementId -> Set<annotationId>\n private replies: Map<string, AnnotationReply[]> = new Map(); // annotationId -> replies[]\n private changesets: Map<string, StagedChangeset> = new Map(); // changesetId -> changeset\n private changesetStorage?: StagedChangesetStorage;\n private authToken: string;\n private authEnabled: boolean = true;\n private withDanger: boolean = false; // Danger mode disabled by default\n private viewerPath?: string; // Optional custom viewer path\n private activeChatProcesses: Map<string, ChildProcess> = new Map(); // conversationId -> spawned chat process\n private chatConversationCounter: number = 0;\n private selectedChatClient?: BaseChatClient; // Selected chat client for server\n private chatInitializationError?: Error; // Store initialization error for status endpoint\n\n /**\n * Valid spec node IDs per layer\n * Dynamically loaded from compiled spec (spec/dist/{layer}.json at module load time)\n * Used to validate and resolve viewer spec node IDs\n * Prevents silent drift when node types are added to spec/schemas/nodes/\n */\n private static readonly VALID_SPEC_NODE_IDS: Record<string, string[]> = VisualizationServer.loadValidSpecNodeIds();\n\n /**\n * Load valid spec node IDs dynamically from bundled spec files\n * Extracts node_types arrays from each layer's compiled schema file\n * This ensures the list is always in sync with the spec build output\n * If any file cannot be loaded, falls back to a minimal safe set\n */\n private static loadValidSpecNodeIds(): Record<string, string[]> {\n const result: Record<string, string[]> = {};\n\n // Get the directory of this file to build absolute path to bundled schemas\n const __dirname = path.dirname(fileURLToPath(import.meta.url));\n const bundledDir = path.join(__dirname, \"..\", \"schemas\", \"bundled\");\n\n for (const layer of CANONICAL_LAYER_NAMES) {\n try {\n // Build absolute path to the layer's compiled spec file\n const specFilePath = path.join(bundledDir, `${layer}.json`);\n\n // Use require() to synchronously load bundled spec file\n // These JSON files are built by \"npm run build\" from spec source\n const spec = require(specFilePath) as {\n layer?: { node_types?: string[] };\n specVersion?: string;\n };\n\n // Extract node_types array from layer object in the compiled spec\n const nodeTypes = spec.layer?.node_types;\n if (Array.isArray(nodeTypes) && nodeTypes.length > 0) {\n result[layer] = nodeTypes;\n } else {\n console.warn(\n `[SPEC-LOAD-001] Layer ${layer} has no node_types in spec file`\n );\n result[layer] = [];\n }\n } catch (error) {\n console.warn(\n `[SPEC-LOAD-002] Failed to load spec node IDs for layer ${layer}: ${getErrorMessage(error)}`\n );\n // Use empty array as fallback - will be caught by usage code\n result[layer] = [];\n }\n }\n\n return result;\n }\n\n /**\n * Fallback spec node IDs used when type extraction from elementId fails\n * These are used when no valid spec node ID can be extracted from the elementId\n * or found through other resolution strategies\n */\n private static readonly LAYER_FALLBACK: Record<string, string> = {\n product: \"business.businessservice\",\n security: \"business.businessprocess\",\n technology: \"application.applicationservice\",\n api: \"business.businessprocess\",\n \"data-model\": \"data-store.database\",\n \"data-store\": \"data-store.database\",\n ux: \"application.applicationcomponent\",\n navigation: \"application.applicationcomponent\",\n apm: \"application.applicationservice\",\n testing: \"business.businessprocess\",\n };\n\n /**\n * All valid spec node IDs across all layers (cached Set for O(1) lookup)\n */\n private static readonly ALL_VALID_IDS: Set<string> = (() => {\n const ids = new Set<string>();\n Object.values(VisualizationServer.VALID_SPEC_NODE_IDS).forEach((layerIds) => {\n layerIds.forEach((id) => ids.add(id));\n });\n return ids;\n })();\n\n constructor(model: Model, options?: VisualizationServerOptions) {\n this.app = new OpenAPIHono();\n this.model = model;\n\n // Set up the WebSocket adapter for this app instance (works under both\n // Node.js and Bun, since @hono/node-server/@hono/node-ws use Node's\n // http module, which Bun also implements).\n this.loadWebSocketAdapter();\n\n // Auth configuration (CLI options override environment variables)\n this.authEnabled = options?.authEnabled ?? process.env.DR_AUTH_ENABLED !== \"false\";\n this.authToken = options?.authToken || process.env.DR_AUTH_TOKEN || this.generateAuthToken();\n this.withDanger = options?.withDanger || false;\n // Use explicit viewerPath if provided, otherwise fall back to the bundled viewer\n this.viewerPath = options?.viewerPath ||\n new URL(\"../viewer\", import.meta.url).pathname;\n\n // Validate cross-field configuration\n if (!this.authEnabled && options?.authToken) {\n console.warn(\n \"[VisualizationServer] Warning: authToken provided but authEnabled is false. \" +\n \"The token will be generated but not used for authentication. \" +\n \"Set authEnabled: true to enable authentication.\"\n );\n }\n\n // Add CORS middleware\n this.app.use(\"/*\", cors());\n\n // Add telemetry middleware to instrument all HTTP requests\n this.app.use(\"/*\", telemetryMiddleware);\n\n // Add authentication middleware (except for health endpoint and root)\n this.app.use(\"/api/*\", async (c, next) => {\n if (!this.authEnabled) {\n return next();\n }\n\n // Check for token in Authorization header or query parameter\n const authHeader = c.req.header(\"Authorization\");\n const queryToken = c.req.query(\"token\");\n\n let providedToken: string | null = null;\n\n if (authHeader && authHeader.startsWith(\"Bearer \")) {\n providedToken = authHeader.substring(7);\n } else if (queryToken) {\n providedToken = queryToken;\n }\n\n if (!providedToken) {\n return c.json({ error: \"Authentication required. Please provide a valid token.\" }, 401);\n }\n\n if (providedToken !== this.authToken) {\n return c.json({ error: \"Invalid authentication token\" }, 403);\n }\n\n return next();\n });\n\n this.setupRoutes();\n\n // Log auth token if enabled\n if (this.authEnabled && process.env.VERBOSE) {\n console.log(`[Auth] Authentication enabled. Token: ${this.authToken}`);\n }\n\n // Initialize chat clients asynchronously\n this.initializeChatClients().catch((error) => {\n this.chatInitializationError = error instanceof Error ? error : new Error(String(error));\n console.error(\"[Chat] Failed to initialize chat clients:\", error);\n });\n }\n\n /**\n * Set up the WebSocket adapter for this server instance\n * This is called in the constructor to support WebSocket functionality via @hono/node-ws,\n * which works identically under Node.js and Bun (both implement Node's http module).\n * Wrapped in try/catch as a defensive guard so a WS setup failure would degrade the\n * server gracefully (no /ws route) rather than crash construction entirely, in case\n * this ever becomes fallible again in the future.\n */\n private loadWebSocketAdapter(): void {\n try {\n const { injectWebSocket, upgradeWebSocket } = createNodeWebSocket({ app: this.app });\n this.injectWebSocket = injectWebSocket;\n this.upgradeWebSocket = upgradeWebSocket;\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n console.warn(\n `[Server] WARNING: Could not set up WebSocket adapter: ${message}. ` +\n `WebSocket endpoints (/ws) will not be available.`\n );\n if (process.env.VERBOSE && error instanceof Error && error.stack) {\n console.debug(\"[Server] Stack trace:\", error.stack);\n }\n }\n }\n\n /**\n * Detect and initialize available chat clients\n */\n private async initializeChatClients(): Promise<void> {\n const clients = await detectAvailableClients();\n\n // Select the chat client from available clients\n // Note: Chat client preference is no longer persisted in manifest\n this.selectedChatClient = selectChatClient(clients, null);\n\n if (this.selectedChatClient && process.env.VERBOSE) {\n console.log(`[Chat] Using chat client: ${this.selectedChatClient.getClientName()}`);\n } else if (clients.length === 0 && process.env.VERBOSE) {\n console.log(\"[Chat] No chat clients available\");\n }\n }\n\n /**\n * Generate a random auth token\n */\n private generateAuthToken(): string {\n return `dr-${randomBytes(24).toString('base64url')}`;\n }\n\n /**\n * Shared annotation update logic for PUT/PATCH handlers\n * Performs partial updates with atomic operations\n */\n private async updateAnnotation(\n annotationId: string,\n body: z.infer<typeof AnnotationUpdateSchema>\n ): Promise<ClientAnnotation | null> {\n const annotation = this.annotations.get(annotationId);\n\n if (!annotation) {\n return null;\n }\n\n // Create new annotation object with updates (atomic update, no direct mutation)\n // This prevents race conditions if the annotation is read during WebSocket broadcast\n const updatedAnnotation: ClientAnnotation = {\n ...annotation,\n content: body.content ?? annotation.content,\n tags: body.tags ?? annotation.tags,\n resolved: body.resolved ?? annotation.resolved,\n updatedAt: new Date().toISOString(),\n };\n\n // Store the updated annotation atomically\n this.annotations.set(annotationId, updatedAnnotation);\n\n // Broadcast to all clients\n await this.broadcastMessage({\n type: \"annotation.updated\",\n annotationId: updatedAnnotation.id,\n timestamp: updatedAnnotation.updatedAt,\n });\n\n return updatedAnnotation;\n }\n\n /**\n * Setup HTTP routes and WebSocket handler\n */\n private setupRoutes(): void {\n // Register bearerAuth security scheme for protected /api/* routes\n this.app.openAPIRegistry.registerComponent('securitySchemes', 'bearerAuth', {\n type: 'http',\n scheme: 'bearer',\n description: 'API token (shown at server startup, pass as Authorization: Bearer <token>)',\n });\n\n // Static viewer HTML at root\n this.app.get(\"/\", (c) => {\n if (this.viewerPath) {\n // Serve custom viewer index.html\n return this.serveCustomViewer(\"index.html\");\n }\n return c.html(this.getViewerHTML());\n });\n\n // Serve static files from custom viewer path if provided\n // Note: This catch-all route must skip API/WS routes by passing to next handler\n if (this.viewerPath) {\n this.app.get(\"/*\", async (c, next) => {\n const requestPath = c.req.path;\n if (process.env.VERBOSE) console.log(`[ROUTE] Catch-all matched: ${requestPath}`);\n\n // Skip API routes and WebSocket - let them be handled by their specific routes\n if (requestPath.startsWith(\"/api/\") || requestPath === \"/ws\" || requestPath === \"/health\" || requestPath === \"/api-spec.yaml\" || requestPath === \"/api-docs\") {\n if (process.env.VERBOSE) console.log(`[ROUTE] Catch-all delegating to next handler for: ${requestPath}`);\n return next(); // Pass to next handler instead of returning 404\n }\n\n if (process.env.VERBOSE) console.log(`[ROUTE] Catch-all serving custom viewer file: ${requestPath}`);\n return this.serveCustomViewer(requestPath.substring(1));\n });\n }\n\n // Health check endpoint\n const healthRoute = createRoute({\n method: 'get',\n path: '/health',\n tags: ['Health'],\n summary: 'Health check',\n description: 'Check if the server is running and healthy',\n responses: {\n 200: {\n description: 'Server is healthy',\n content: {\n 'application/json': {\n schema: HealthResponseSchema,\n },\n },\n },\n },\n });\n\n this.app.openapi(healthRoute, (c) => {\n const response: any = {\n status: this.chatInitializationError ? \"degraded\" : \"ok\",\n version: \"0.1.0\",\n };\n\n // Include warnings if any services failed to initialize\n if (this.chatInitializationError) {\n response.warnings = [\n `Chat service unavailable: ${getErrorMessage(this.chatInitializationError)}`\n ];\n }\n\n return c.json(response);\n });\n\n // Get full model\n const getModelRoute = createRoute({\n method: 'get',\n path: '/api/model',\n tags: ['Model'],\n summary: 'Get full model',\n description: 'Retrieve the complete architecture model across all layers',\n security: [{ bearerAuth: [] }],\n responses: {\n 200: {\n description: 'Model data retrieved successfully',\n content: {\n 'application/json': {\n schema: ModelResponseSchema,\n },\n },\n },\n 500: {\n description: 'Server error',\n content: {\n 'application/json': {\n schema: ErrorResponseSchema,\n },\n },\n },\n },\n });\n\n this.app.openapi(getModelRoute, async (c) => {\n if (process.env.VERBOSE) console.log(`[ROUTE] /api/model handler called`);\n try {\n if (process.env.VERBOSE) console.log(`[ROUTE] /api/model serializing model...`);\n const modelData = await this.serializeModel();\n if (process.env.VERBOSE)\n console.log(\n `[ROUTE] /api/model returning ${Object.keys(modelData.layers || {}).length} layers`\n );\n return c.json(modelData);\n } catch (error) {\n const message = getErrorMessage(error);\n console.error(`[ROUTE] /api/model error: ${message}`);\n return c.json({ error: message }, 500);\n }\n });\n\n // Get specific layer\n const getLayerRoute = createRoute({\n method: 'get',\n path: '/api/layers/:layerName',\n tags: ['Model'],\n summary: 'Get layer',\n description: 'Retrieve a specific layer with all its elements',\n security: [{ bearerAuth: [] }],\n request: {\n params: z.object({ layerName: LayerNameSchema }),\n },\n responses: {\n 200: {\n description: 'Layer retrieved successfully',\n content: {\n 'application/json': {\n schema: LayerResponseSchema,\n },\n },\n },\n 404: {\n description: 'Layer not found',\n content: {\n 'application/json': {\n schema: ErrorResponseSchema,\n },\n },\n },\n 500: {\n description: 'Server error',\n content: {\n 'application/json': {\n schema: ErrorResponseSchema,\n },\n },\n },\n },\n });\n\n // \u2705 Type cast required: See Type Casting Note at top of file\n this.app.openapi(getLayerRoute, (async (c: any) => {\n try {\n const { layerName } = c.req.valid(\"param\");\n const layer = await this.model.getLayer(layerName);\n\n if (!layer) {\n return c.json({ error: \"Layer not found\" }, 404);\n }\n\n const elements = layer.listElements().map((e) => e.toJSON());\n\n return c.json({\n name: layerName,\n elements,\n elementCount: elements.length,\n });\n } catch (error) {\n const message = getErrorMessage(error);\n return c.json({ error: message }, 500);\n }\n }) as any);\n\n // Get JSON Schema specifications\n const getSpecRoute = createRoute({\n method: 'get',\n path: '/api/spec',\n tags: ['Schema'],\n summary: 'Get JSON schemas',\n description: 'Retrieve all JSON Schema definitions used by the system',\n security: [{ bearerAuth: [] }],\n responses: {\n 200: {\n description: 'Schemas retrieved successfully',\n content: {\n 'application/json': {\n schema: SpecResponseSchema,\n },\n },\n },\n 500: {\n description: 'Server error',\n content: {\n 'application/json': {\n schema: ErrorResponseSchema,\n },\n },\n },\n },\n });\n\n // \u2705 Type cast required: See Type Casting Note at top of file\n this.app.openapi(getSpecRoute, (async (c: any) => {\n try {\n const schemas = await this.loadSchemas();\n return c.json({\n version: \"0.1.0\",\n type: \"schema-collection\",\n description: \"JSON Schema definitions from dr CLI\",\n source: \"dr-cli\",\n schemas,\n schemaCount: Object.keys(schemas).length,\n });\n } catch (error) {\n const message = getErrorMessage(error);\n return c.json({ error: message }, 500);\n }\n }) as any);\n\n // Annotations API (spec-compliant routes)\n\n // Get all annotations (optionally filtered by elementId)\n const getAnnotationsRoute = createRoute({\n method: 'get',\n path: '/api/annotations',\n tags: ['Annotations'],\n summary: 'Get annotations',\n description: 'Retrieve all annotations, optionally filtered by element',\n security: [{ bearerAuth: [] }],\n request: {\n query: AnnotationFilterSchema,\n },\n responses: {\n 200: {\n description: 'Annotations retrieved successfully',\n content: {\n 'application/json': {\n schema: AnnotationsListSchema,\n },\n },\n },\n },\n });\n\n this.app.openapi(getAnnotationsRoute, (c) => {\n const query = c.req.valid(\"query\");\n\n const elementId = query.elementId;\n\n if (elementId) {\n // Filter by element\n const annotationIds = this.annotationsByElement.get(elementId) || new Set();\n const annotations = Array.from(annotationIds)\n .map((id) => this.annotations.get(id))\n .filter((a): a is ClientAnnotation => a !== undefined);\n\n return c.json({ annotations });\n } else {\n // Return all annotations\n const annotations = Array.from(this.annotations.values());\n return c.json({ annotations });\n }\n });\n\n // Create annotation\n const createAnnotationRoute = createRoute({\n method: 'post',\n path: '/api/annotations',\n tags: ['Annotations'],\n summary: 'Create annotation',\n description: 'Create a new annotation on a model element',\n security: [{ bearerAuth: [] }],\n request: {\n body: {\n content: {\n 'application/json': {\n schema: AnnotationCreateSchema,\n },\n },\n },\n },\n responses: {\n 201: {\n description: 'Annotation created successfully',\n content: {\n 'application/json': {\n schema: AnnotationSchema,\n },\n },\n },\n 400: {\n description: 'Invalid request body',\n content: {\n 'application/json': {\n schema: ErrorResponseSchema,\n },\n },\n },\n 404: {\n description: 'Element not found',\n content: {\n 'application/json': {\n schema: ErrorResponseSchema,\n },\n },\n },\n 500: {\n description: 'Server error',\n content: {\n 'application/json': {\n schema: ErrorResponseSchema,\n },\n },\n },\n },\n });\n\n this.app.openapi(createAnnotationRoute, async (c) => {\n try {\n const body = c.req.valid(\"json\");\n\n // Verify element exists\n const element = await this.findElement(body.elementId);\n if (!element) {\n return c.json({ error: \"Element not found\" }, 404);\n }\n\n // Create annotation\n const annotation: ClientAnnotation = {\n id: this.generateAnnotationId(),\n elementId: body.elementId,\n author: body.author,\n content: body.content,\n createdAt: new Date().toISOString(),\n tags: body.tags,\n resolved: false, // Default to unresolved\n };\n\n // Store annotation\n this.annotations.set(annotation.id, annotation);\n if (!this.annotationsByElement.has(annotation.elementId)) {\n this.annotationsByElement.set(annotation.elementId, new Set());\n }\n this.annotationsByElement.get(annotation.elementId)!.add(annotation.id);\n\n // Broadcast to all clients\n await this.broadcastMessage({\n type: \"annotation.added\",\n annotationId: annotation.id,\n elementId: annotation.elementId,\n timestamp: annotation.createdAt,\n });\n\n return c.json(annotation, 201);\n } catch (error) {\n const message = getErrorMessage(error);\n return c.json({ error: message }, 500);\n }\n });\n\n // Get individual annotation by ID\n const getAnnotationRoute = createRoute({\n method: 'get',\n path: '/api/annotations/:annotationId',\n tags: ['Annotations'],\n summary: 'Get annotation by ID',\n description: 'Retrieve a specific annotation by its ID',\n security: [{ bearerAuth: [] }],\n request: {\n params: z.object({ annotationId: IdSchema }),\n },\n responses: {\n 200: {\n description: 'Annotation retrieved successfully',\n content: {\n 'application/json': {\n schema: AnnotationSchema,\n },\n },\n },\n 404: {\n description: 'Annotation not found',\n content: {\n 'application/json': {\n schema: ErrorResponseSchema,\n },\n },\n },\n 500: {\n description: 'Server error',\n content: {\n 'application/json': {\n schema: ErrorResponseSchema,\n },\n },\n },\n },\n });\n\n // \u2705 Type cast required: See Type Casting Note at top of file\n this.app.openapi(getAnnotationRoute, (async (c: any) => {\n try {\n const { annotationId } = c.req.valid(\"param\");\n const annotation = this.annotations.get(annotationId);\n\n if (!annotation) {\n return c.json({ error: \"Annotation not found\" }, 404);\n }\n\n return c.json(annotation);\n } catch (error) {\n const message = getErrorMessage(error);\n return c.json({ error: message }, 500);\n }\n }) as any);\n\n // Update annotation (PUT - partial update for backwards compatibility)\n // NOTE: DESIGN RATIONALE - Non-standard PUT behavior:\n // This endpoint performs PARTIAL updates (idempotent) like PATCH, not full replacement\n // like standard HTTP PUT. This is a known deviation from HTTP semantics, chosen for\n // backwards compatibility with existing API consumers. New code should prefer PATCH\n // for partial updates. If full replacement semantics are needed in future, a new\n // endpoint should be created rather than changing this one's behavior.\n const putAnnotationRoute = createRoute({\n method: 'put',\n path: '/api/annotations/:annotationId',\n tags: ['Annotations'],\n summary: 'Update annotation (partial)',\n description: 'Update an existing annotation with partial fields (NOTE: partial updates, use PATCH for recommended behavior)',\n security: [{ bearerAuth: [] }],\n request: {\n params: z.object({ annotationId: IdSchema }),\n body: {\n content: {\n 'application/json': {\n schema: AnnotationUpdateSchema,\n },\n },\n },\n },\n responses: {\n 200: {\n description: 'Annotation updated successfully',\n content: {\n 'application/json': {\n schema: AnnotationSchema,\n },\n },\n },\n 404: {\n description: 'Annotation not found',\n content: {\n 'application/json': {\n schema: ErrorResponseSchema,\n },\n },\n },\n 500: {\n description: 'Server error',\n content: {\n 'application/json': {\n schema: ErrorResponseSchema,\n },\n },\n },\n },\n });\n\n // \u2705 Type cast required: See Type Casting Note at top of file\n this.app.openapi(putAnnotationRoute, (async (c: any) => {\n try {\n const { annotationId } = c.req.valid(\"param\");\n const body = c.req.valid(\"json\");\n\n const updatedAnnotation = await this.updateAnnotation(annotationId, body);\n\n if (!updatedAnnotation) {\n return c.json({ error: \"Annotation not found\" }, 404);\n }\n\n return c.json(updatedAnnotation);\n } catch (error) {\n const message = getErrorMessage(error);\n return c.json({ error: message }, 500);\n }\n }) as any);\n\n // Update annotation (PATCH)\n const patchAnnotationRoute = createRoute({\n method: 'patch',\n path: '/api/annotations/:annotationId',\n tags: ['Annotations'],\n summary: 'Partially update annotation',\n description: 'Partially update an existing annotation (recommended over PUT)',\n security: [{ bearerAuth: [] }],\n request: {\n params: z.object({ annotationId: IdSchema }),\n body: {\n content: {\n 'application/json': {\n schema: AnnotationUpdateSchema,\n },\n },\n },\n },\n responses: {\n 200: {\n description: 'Annotation updated successfully',\n content: {\n 'application/json': {\n schema: AnnotationSchema,\n },\n },\n },\n 404: {\n description: 'Annotation not found',\n content: {\n 'application/json': {\n schema: ErrorResponseSchema,\n },\n },\n },\n 500: {\n description: 'Server error',\n content: {\n 'application/json': {\n schema: ErrorResponseSchema,\n },\n },\n },\n },\n });\n\n // \u2705 Type cast required: See Type Casting Note at top of file\n this.app.openapi(patchAnnotationRoute, (async (c: any) => {\n try {\n const { annotationId } = c.req.valid(\"param\");\n const body = c.req.valid(\"json\");\n\n const updatedAnnotation = await this.updateAnnotation(annotationId, body);\n\n if (!updatedAnnotation) {\n return c.json({ error: \"Annotation not found\" }, 404);\n }\n\n return c.json(updatedAnnotation);\n } catch (error) {\n const message = getErrorMessage(error);\n return c.json({ error: message }, 500);\n }\n }) as any);\n\n // Delete annotation\n const deleteAnnotationRoute = createRoute({\n method: 'delete',\n path: '/api/annotations/:annotationId',\n tags: ['Annotations'],\n summary: 'Delete annotation',\n description: 'Delete an existing annotation',\n security: [{ bearerAuth: [] }],\n request: {\n params: z.object({ annotationId: IdSchema }),\n },\n responses: {\n 204: {\n description: 'Annotation deleted successfully',\n },\n 404: {\n description: 'Annotation not found',\n content: {\n 'application/json': {\n schema: ErrorResponseSchema,\n },\n },\n },\n 500: {\n description: 'Server error',\n content: {\n 'application/json': {\n schema: ErrorResponseSchema,\n },\n },\n },\n },\n });\n\n this.app.openapi(deleteAnnotationRoute, async (c) => {\n try {\n const { annotationId } = c.req.valid(\"param\");\n const annotation = this.annotations.get(annotationId);\n\n if (!annotation) {\n return c.json({ error: \"Annotation not found\" }, 404);\n }\n\n // Remove from storage\n this.annotations.delete(annotationId);\n this.replies.delete(annotationId); // Clean up replies\n const elementAnnotations = this.annotationsByElement.get(annotation.elementId);\n if (elementAnnotations) {\n elementAnnotations.delete(annotationId);\n }\n\n // Broadcast to all clients\n await this.broadcastMessage({\n type: \"annotation.deleted\",\n annotationId,\n timestamp: new Date().toISOString(),\n });\n\n return c.body(null, 204);\n } catch (error) {\n const message = getErrorMessage(error);\n return c.json({ error: message }, 500);\n }\n });\n\n // GET annotation replies\n const getAnnotationRepliesRoute = createRoute({\n method: 'get',\n path: '/api/annotations/:annotationId/replies',\n tags: ['Annotations'],\n summary: 'Get annotation replies',\n description: 'Retrieve all replies to an annotation',\n security: [{ bearerAuth: [] }],\n request: {\n params: z.object({ annotationId: IdSchema }),\n },\n responses: {\n 200: {\n description: 'Replies retrieved successfully',\n content: {\n 'application/json': {\n schema: AnnotationRepliesSchema,\n },\n },\n },\n 404: {\n description: 'Annotation not found',\n content: {\n 'application/json': {\n schema: ErrorResponseSchema,\n },\n },\n },\n },\n });\n\n // \u2705 Type cast required: See Type Casting Note at top of file\n this.app.openapi(getAnnotationRepliesRoute, ((c: any) => {\n const { annotationId } = c.req.valid(\"param\");\n const annotation = this.annotations.get(annotationId);\n\n if (!annotation) {\n return c.json({ error: \"Annotation not found\" }, 404);\n }\n\n const replies = this.replies.get(annotationId) || [];\n return c.json({ replies });\n }) as any);\n\n // POST annotation reply\n const createAnnotationReplyRoute = createRoute({\n method: 'post',\n path: '/api/annotations/:annotationId/replies',\n tags: ['Annotations'],\n summary: 'Create annotation reply',\n description: 'Add a reply to an annotation',\n security: [{ bearerAuth: [] }],\n request: {\n params: z.object({ annotationId: IdSchema }),\n body: {\n content: {\n 'application/json': {\n schema: AnnotationReplyCreateSchema,\n },\n },\n },\n },\n responses: {\n 201: {\n description: 'Reply created successfully',\n content: {\n 'application/json': {\n schema: AnnotationReplySchema,\n },\n },\n },\n 404: {\n description: 'Annotation not found',\n content: {\n 'application/json': {\n schema: ErrorResponseSchema,\n },\n },\n },\n 500: {\n description: 'Server error',\n content: {\n 'application/json': {\n schema: ErrorResponseSchema,\n },\n },\n },\n },\n });\n\n this.app.openapi(createAnnotationReplyRoute, async (c) => {\n try {\n const { annotationId } = c.req.valid(\"param\");\n const annotation = this.annotations.get(annotationId);\n\n if (!annotation) {\n return c.json({ error: \"Annotation not found\" }, 404);\n }\n\n const body = c.req.valid(\"json\");\n\n const reply: AnnotationReply = {\n id: this.generateReplyId(),\n author: body.author,\n content: body.content,\n createdAt: new Date().toISOString(),\n };\n\n // Store reply\n if (!this.replies.has(annotationId)) {\n this.replies.set(annotationId, []);\n }\n this.replies.get(annotationId)!.push(reply);\n\n // Broadcast to all clients\n await this.broadcastMessage({\n type: \"annotation.reply.added\",\n annotationId,\n replyId: reply.id,\n timestamp: reply.createdAt,\n });\n\n return c.json(reply, 201);\n } catch (error) {\n const message = getErrorMessage(error);\n return c.json({ error: message }, 500);\n }\n });\n\n // Changesets API\n\n // Get all changesets\n const getChangesetsRoute = createRoute({\n method: 'get',\n path: '/api/changesets',\n tags: ['Changesets'],\n summary: 'Get changesets',\n description: 'Retrieve a list of all changesets',\n security: [{ bearerAuth: [] }],\n responses: {\n 200: {\n description: 'Changesets retrieved successfully',\n content: {\n 'application/json': {\n schema: ChangesetsListSchema,\n },\n },\n },\n },\n });\n\n this.app.openapi(getChangesetsRoute, (c) => {\n const changesets: Record<string, any> = {};\n\n for (const [id, changeset] of this.changesets) {\n changesets[id] = {\n name: changeset.name,\n status: changeset.status,\n created: changeset.created,\n changes_count: changeset.changes.length,\n };\n }\n\n return c.json({\n version: \"1.0.0\",\n changesets,\n });\n });\n\n // Get specific changeset\n const getChangesetRoute = createRoute({\n method: 'get',\n path: '/api/changesets/:changesetId',\n tags: ['Changesets'],\n summary: 'Get changeset',\n description: 'Retrieve a specific changeset with all its changes',\n security: [{ bearerAuth: [] }],\n request: {\n params: z.object({ changesetId: IdSchema }),\n },\n responses: {\n 200: {\n description: 'Changeset retrieved successfully',\n content: {\n 'application/json': {\n schema: ChangesetDetailSchema,\n },\n },\n },\n 404: {\n description: 'Changeset not found',\n content: {\n 'application/json': {\n schema: ErrorResponseSchema,\n },\n },\n },\n },\n });\n\n // \u2705 Type cast required: See Type Casting Note at top of file\n this.app.openapi(getChangesetRoute, ((c: any) => {\n const { changesetId } = c.req.valid(\"param\");\n const changeset = this.changesets.get(changesetId);\n\n if (!changeset) {\n return c.json({ error: \"Changeset not found\" }, 404);\n }\n\n return c.json({\n id: changeset.id,\n name: changeset.name,\n description: changeset.description,\n status: changeset.status,\n created: changeset.created,\n modified: changeset.modified,\n baseSnapshot: changeset.baseSnapshot,\n stats: changeset.stats,\n changes: changeset.changes,\n });\n }) as any);\n\n // OpenAPI documentation endpoint\n // Note: @hono/zod-openapi always generates 3.1.0, so we specify it here\n // but the version is normalized to 3.0.3 in the generate-openapi.ts script\n this.app.doc('/api-spec.yaml', {\n openapi: '3.1.0',\n info: {\n title: 'Documentation Robotics Visualization Server API',\n version: '0.1.0',\n description: 'API specification for the DR CLI visualization server',\n contact: {\n name: 'Documentation Robotics',\n url: 'https://github.com/tinkermonkey/documentation_robotics',\n },\n license: {\n name: 'ISC',\n },\n },\n servers: [\n { url: 'http://localhost:8080', description: 'Local development server' },\n ],\n tags: [\n { name: 'Health', description: 'Server health and status' },\n { name: 'Schema', description: 'JSON Schema specifications' },\n { name: 'Model', description: 'Architecture model data' },\n { name: 'Changesets', description: 'Model changesets and history' },\n { name: 'Annotations', description: 'User annotations on model elements' },\n { name: 'WebSocket', description: 'Real-time updates via WebSocket' },\n ],\n });\n\n // Swagger UI for interactive API exploration\n this.app.get('/api-docs', swaggerUI({ url: '/api-spec.yaml' }));\n\n // WebSocket endpoint\n // Only register if the WebSocket adapter was set up successfully\n if (this.upgradeWebSocket) {\n this.app.get(\n \"/ws\",\n async (c, next) => {\n // Check WebSocket auth if enabled\n if (this.authEnabled) {\n // Accept token from multiple sources to support diverse client environments:\n // 1. Query parameter: ?token=<token>\n // Why: Works in all browsers. Simple and universally compatible.\n // 2. Sec-WebSocket-Protocol header: token, <actual-token>\n // Why: Browsers cannot set custom HTTP headers during WebSocket handshake\n // for security reasons. The WebSocket spec allows subprotocol negotiation\n // as an alternative auth mechanism. Format: [\"token\", \"YOUR_TOKEN\"]\n // 3. Authorization header: Bearer <token>\n // Why: Standard OAuth2/JWT pattern for non-browser clients (Node.js, cURL, etc)\n // that can set arbitrary headers. Browsers cannot use this for WebSocket due\n // to HTTP header restrictions during handshake.\n\n const queryToken = c.req.query(\"token\");\n\n // Check Sec-WebSocket-Protocol header (browser-compatible method)\n // Format: \"token, <actual-token>\" or \"token,<actual-token>\"\n const wsProtocol = c.req.header(\"Sec-WebSocket-Protocol\");\n let protocolToken: string | null = null;\n if (wsProtocol) {\n const protocols = wsProtocol.split(\",\").map((p) => p.trim());\n // Look for protocol pair: ['token', '<actual-token>']\n const tokenIndex = protocols.indexOf(\"token\");\n if (tokenIndex !== -1 && protocols.length > tokenIndex + 1) {\n protocolToken = protocols[tokenIndex + 1];\n }\n }\n\n // Check Authorization header (non-browser clients)\n const authHeader = c.req.header(\"Authorization\");\n const bearerToken = authHeader?.startsWith(\"Bearer \") ? authHeader.substring(7) : null;\n\n const token = queryToken || protocolToken || bearerToken;\n\n if (!token) {\n return c.json(\n {\n error:\n 'Authentication required. Provide token via: 1) Query param (?token=YOUR_TOKEN), 2) Sec-WebSocket-Protocol header (browser: new WebSocket(url, [\"token\", \"YOUR_TOKEN\"])), or 3) Authorization header (Bearer YOUR_TOKEN)',\n },\n 401\n );\n }\n\n if (token !== this.authToken) {\n return c.json({ error: \"Invalid authentication token\" }, 403);\n }\n\n // If token came from Sec-WebSocket-Protocol, we need to respond with the protocol\n // This is required by the WebSocket spec for subprotocol negotiation\n if (protocolToken) {\n c.header(\"Sec-WebSocket-Protocol\", \"token\");\n }\n }\n\n return next();\n },\n this.upgradeWebSocket(() => ({\n onOpen: async (_evt: any, ws: HonoWSContext) => {\n // Telemetry: Track WebSocket connection\n await this.recordWebSocketEvent(\"ws.connection.open\", {\n \"ws.client_count\": this.clients.size + 1,\n });\n\n this.clients.add(ws);\n\n // Send connected message per spec\n ws.send(\n JSON.stringify({\n type: \"connected\",\n version: \"0.1.0\",\n timestamp: new Date().toISOString(),\n })\n );\n\n if (process.env.VERBOSE) {\n console.log(`[WebSocket] Client connected (total: ${this.clients.size})`);\n }\n },\n\n onClose: async (_evt: any, ws: HonoWSContext) => {\n // Telemetry: Track WebSocket disconnection\n await this.recordWebSocketEvent(\"ws.connection.close\", {\n \"ws.client_count\": this.clients.size - 1,\n });\n\n this.clients.delete(ws);\n if (process.env.VERBOSE) {\n console.log(`[WebSocket] Client disconnected (total: ${this.clients.size})`);\n }\n },\n\n onMessage: async (message: any, ws: HonoWSContext) => {\n const messageStartTime = Date.now();\n let messageType = \"unknown\";\n\n try {\n // Extract the actual message data\n // In Hono's WebSocket helper, message is a MessageEvent with a data property\n const rawData = (message as any).data ?? message;\n\n // Handle different message types (string, Buffer, ArrayBuffer)\n let msgStr: string;\n if (typeof rawData === \"string\") {\n msgStr = rawData;\n } else if (rawData instanceof Buffer || rawData instanceof Uint8Array) {\n msgStr = new TextDecoder().decode(rawData);\n } else {\n msgStr = String(rawData);\n }\n const rawMessage = JSON.parse(msgStr);\n\n // Runtime validation of WebSocket message format\n const validationResult = WSMessageSchema.safeParse(rawMessage);\n if (!validationResult.success) {\n const errorMsg = validationResult.error.issues.map((e) => `${e.path.join('.')}: ${e.message}`).join('; ');\n // Always log validation errors for production debugging\n console.error(\"Invalid WebSocket message format:\", errorMsg);\n ws.send(JSON.stringify({ error: \"Invalid message format\", details: errorMsg }));\n return;\n }\n\n const data = validationResult.data as WSMessage;\n\n // Type-safe discriminated union handling\n if ('jsonrpc' in data && data.jsonrpc === '2.0') {\n // Check if it's a request (has method) or response (has result/error)\n if ('method' in data && typeof data.method === 'string') {\n messageType = (data as JSONRPCRequest).method;\n } else {\n messageType = (data as JSONRPCResponse).id?.toString() || \"jsonrpc-response\";\n }\n } else if ('type' in data) {\n messageType = (data as SimpleWSMessage).type;\n } else {\n messageType = \"unknown\";\n }\n\n // Telemetry: Track message processing\n await this.recordWebSocketEvent(\"ws.message.received\", {\n \"ws.message.type\": messageType,\n \"ws.message.size_bytes\": msgStr.length,\n });\n\n // Check if it's a JSON-RPC message\n if ('jsonrpc' in data && data.jsonrpc === \"2.0\") {\n await this.handleJSONRPCMessage(ws, data);\n } else {\n await this.handleWSMessage(ws, data);\n }\n\n // Telemetry: Track successful message processing\n const durationMs = Date.now() - messageStartTime;\n await this.recordWebSocketEvent(\"ws.message.processed\", {\n \"ws.message.type\": messageType,\n \"ws.message.duration_ms\": durationMs,\n \"ws.message.status\": \"success\",\n });\n } catch (error) {\n const errorMsg = getErrorMessage(error);\n const durationMs = Date.now() - messageStartTime;\n\n // Telemetry: Track message processing error\n await this.recordWebSocketEvent(\"ws.message.error\", {\n \"ws.message.type\": messageType,\n \"ws.message.duration_ms\": durationMs,\n \"ws.message.status\": \"error\",\n \"error.message\": errorMsg,\n });\n\n // Always log message handling errors for operational visibility\n console.warn(\n `[WebSocket] Error handling ${messageType} message (${durationMs}ms): ${errorMsg}`\n );\n ws.send(\n JSON.stringify({\n type: \"error\",\n message: errorMsg,\n })\n );\n }\n },\n\n onError: async (error: any) => {\n const message = getErrorMessage(error);\n\n // Telemetry: Track WebSocket error\n await this.recordWebSocketEvent(\"ws.error\", {\n \"error.type\": error instanceof Error ? error.constructor.name : \"unknown\",\n \"error.message\": message,\n });\n\n // Always log WebSocket errors with full context including stack trace for debugging\n console.error(\"[WebSocket] Error:\", error);\n },\n }))\n );\n }\n }\n\n /**\n * Get OpenAPI specification document\n * Provides a stable public API for OpenAPI spec generation.\n *\n * BEHAVIOR: Returns an OpenAPI spec with the version specified in `config.openapi`.\n * The returned document will have whatever OpenAPI version was passed in the config.\n *\n * NOTE: Hono's getOpenAPI31Document() passes the config directly through and\n * uses the specified version in the output. There is no forced version conversion.\n */\n public getOpenAPIDocument(config: {\n openapi: string;\n info: {\n title: string;\n version: string;\n description?: string;\n contact?: { name?: string; url?: string };\n license?: { name: string };\n };\n servers?: Array<{ url: string; description?: string }>;\n }): any {\n return this.app.getOpenAPI31Document(config);\n }\n\n /**\n * Serialize model to graph format for client consumption.\n *\n * Returns { nodes, links } where:\n * - nodes: flat array of all elements across all layers\n * - links: intra-layer relationships (carry layer_id) and cross-layer\n * references (carry source_layer_id + target_layer_id)\n *\n * Links are deduplicated: if the same relationship appears on multiple\n * elements (e.g. stored on both source and target), only one entry is kept.\n */\n private async serializeModel(): Promise<any> {\n const nodes: any[] = [];\n const linksMap = new Map<string, any>();\n\n // Build element-id \u2192 layer_id lookup so cross-layer reference links can\n // carry accurate source_layer_id / target_layer_id even when the\n // referenced element lives in a different layer.\n const elementLayerMap = new Map<string, string>();\n for (const [layerName, layer] of this.model.layers) {\n for (const e of layer.listElements()) {\n const layerId = e.layer_id || layerName;\n if (e.id) elementLayerMap.set(e.id, layerId);\n // Also index by path so references that use dot-separated slugs resolve correctly.\n // For new-format elements: e.id is a UUID, e.path is the dot-separated slug.\n // For old-format elements: e.id === e.path (both the slug), so no duplicate work.\n if (e.path && e.path !== e.id) elementLayerMap.set(e.path, layerId);\n }\n }\n\n for (const [layerName, layer] of this.model.layers) {\n for (const e of layer.listElements()) {\n const layerId = e.layer_id || layerName;\n\n // Resolve a viewer-compatible spec_node_id so every element renders\n // correctly in the v0.3.0 viewer regardless of which layer it belongs to.\n const viewerSpecNodeId = this.resolveViewerSpecNodeId(e.id, layerId, e.type, e.spec_node_id);\n\n // Node \u2014 all model fields for complete client-side access\n const node: any = {\n id: e.id,\n spec_node_id: viewerSpecNodeId,\n type: e.type,\n layer_id: layerId,\n name: e.name,\n };\n if (e.description) node.description = e.description;\n if (e.attributes && Object.keys(e.attributes).length > 0) node.attributes = e.attributes;\n if (e.source_reference) node.source_reference = e.source_reference;\n if (e.metadata) node.metadata = e.metadata;\n\n nodes.push(node);\n\n // Intra-layer links from relationships\n for (const rel of e.relationships) {\n const linkId = `rel:${rel.source}:${rel.target}:${rel.predicate}`;\n if (!linksMap.has(linkId)) {\n linksMap.set(linkId, {\n id: linkId,\n source: rel.source,\n target: rel.target,\n type: rel.predicate,\n layer_id: layerId,\n });\n }\n }\n\n // Cross-layer links from references\n for (const ref of e.references) {\n const linkId = `ref:${ref.source}:${ref.target}:${ref.type}`;\n if (!linksMap.has(linkId)) {\n linksMap.set(linkId, {\n id: linkId,\n source: ref.source,\n target: ref.target,\n type: ref.type,\n source_layer_id: elementLayerMap.get(ref.source) || layerId,\n target_layer_id: elementLayerMap.get(ref.target) || 'unknown',\n });\n }\n }\n }\n }\n\n // Central relationships.yaml links (added via `dr relationship add`)\n // These are NOT attached to individual elements, so the per-element loop\n // above misses them entirely. Emit them here using the same linksMap so\n // duplicates are naturally de-duplicated.\n for (const rel of this.model.relationships.getAll()) {\n const linkId = `rel:${rel.source}:${rel.target}:${rel.predicate}`;\n if (!linksMap.has(linkId)) {\n const layerId = rel.layer || elementLayerMap.get(rel.source);\n if (!layerId) continue; // Skip \u2014 source element not found and no layer field set\n const link: any = {\n id: linkId,\n source: rel.source,\n target: rel.target,\n type: rel.predicate,\n };\n // Cross-layer relationships set targetLayer; intra-layer relationships do not.\n // Both paths are reachable through normal CLI usage via `dr relationship add`.\n if (rel.targetLayer) {\n link.source_layer_id = layerId;\n link.target_layer_id = rel.targetLayer;\n } else {\n link.layer_id = layerId;\n }\n linksMap.set(linkId, link);\n }\n }\n\n return {\n nodes,\n links: Array.from(linksMap.values()),\n };\n }\n\n /**\n * Resolve viewer spec node ID with fallback logic\n * Handles multiple resolution strategies:\n * 1. Valid pass-through: Return specNodeId if it's already a valid spec node ID\n * 2. Dot-format extraction: Extract type from elementId in \"layer.type.name\" format and construct candidate\n * 3. Hyphen-format extraction: Extract type from elementId in \"layer-type-name\" format and construct candidate\n * 4. Type-parameter fallback: If extracted type differs from type parameter, retry with type parameter\n * 5. Layer-specific fallback: Use LAYER_FALLBACK map for layers with limited viewer support\n *\n * @param elementId - The element ID (can be in dot-format or hyphen-format)\n * @param layer - The layer name (canonical form)\n * @param type - The element type field\n * @param specNodeId - The spec node ID if already known\n * @returns The resolved spec node ID string\n */\n private resolveViewerSpecNodeId(\n elementId: string,\n layer: string,\n type: string,\n specNodeId: string\n ): string {\n // Strategy 1: Valid pass-through - return if specNodeId is valid (anywhere in the model)\n if (specNodeId && VisualizationServer.ALL_VALID_IDS.has(specNodeId)) {\n return specNodeId;\n }\n\n // Strategy 2 & 3: Extract type from elementId and construct candidate\n // Only extract if elementId is not empty\n let extractedType: string | null = null;\n\n if (elementId) {\n // Try dot-format extraction first (layer.type.name)\n if (elementId.includes(\".\")) {\n const parts = elementId.split(\".\");\n if (parts.length >= 2) {\n extractedType = parts[1]; // Second segment is the type\n }\n }\n\n // If no dot-format extraction, try hyphen-format (layer-type-name)\n if (!extractedType && elementId.includes(\"-\")) {\n // For multi-word layers like \"data-model\" or \"data-store\", handle specially\n if (layer === \"data-model\" || layer === \"data-store\") {\n // These layers have hyphens in their names\n // For \"data-model-entity-users\", we need to extract after \"data-model-\"\n const expectedPrefix = layer + \"-\";\n if (elementId.startsWith(expectedPrefix)) {\n const afterPrefix = elementId.substring(expectedPrefix.length);\n const parts = afterPrefix.split(\"-\");\n if (parts.length >= 1) {\n extractedType = parts[0]; // First segment after layer prefix is the type\n }\n }\n } else {\n // For single-word layers, split by hyphen and take second part\n const parts = elementId.split(\"-\");\n if (parts.length >= 2) {\n // Second segment is the type (first is layer)\n extractedType = parts[1];\n }\n }\n }\n }\n\n // If we extracted a type, try to construct a valid candidate\n if (extractedType) {\n const candidate = `${layer}.${extractedType}`;\n const layerIds = VisualizationServer.VALID_SPEC_NODE_IDS[layer] || [];\n if (layerIds.includes(candidate)) {\n return candidate;\n }\n }\n\n // Strategy 4: If type param was provided, try with provided type\n // This applies when:\n // - extractedType is null (e.g., unparseable elementId) but type is valid\n // - extractedType differs from type (prefer extracted over provided)\n if (type && (!extractedType || extractedType !== type)) {\n const candidate = `${layer}.${type}`;\n const layerIds = VisualizationServer.VALID_SPEC_NODE_IDS[layer] || [];\n if (layerIds.includes(candidate)) {\n return candidate;\n }\n }\n\n // Strategy 5: Fall back to layer-specific fallback\n if (layer in VisualizationServer.LAYER_FALLBACK) {\n return VisualizationServer.LAYER_FALLBACK[layer];\n }\n\n // Default fallback for unknown layers\n return \"data-store.database\";\n }\n\n /**\n * Generate unique annotation ID\n * Uses cryptographically secure random bytes for collision-resistant IDs\n */\n private generateAnnotationId(): string {\n return `ann-${Date.now()}-${randomBytes(4).toString('hex')}`;\n }\n\n /**\n * Generate unique reply ID\n * Uses cryptographically secure random bytes for collision-resistant IDs\n */\n private generateReplyId(): string {\n return `reply-${Date.now()}-${randomBytes(4).toString('hex')}`;\n }\n\n /**\n * Broadcast message to all connected WebSocket clients\n */\n private async broadcastMessage(message: any): Promise<void> {\n const messageStr = JSON.stringify(message);\n const messageType = message.type || \"unknown\";\n\n // Telemetry: Track broadcast start\n await this.recordWebSocketEvent(\"ws.broadcast.start\", {\n \"ws.broadcast.type\": messageType,\n \"ws.broadcast.client_count\": this.clients.size,\n \"ws.broadcast.message_size_bytes\": messageStr.length,\n });\n\n let failureCount = 0;\n const failedClients: HonoWSContext[] = [];\n\n for (const client of this.clients) {\n try {\n client.send(messageStr);\n } catch (error) {\n failureCount++;\n failedClients.push(client);\n const msg = getErrorMessage(error);\n // Always log broadcast failures for operational visibility in production\n console.warn(`[WebSocket] Failed to send message to client: ${msg}`);\n }\n }\n\n // Remove dead clients from the active client set\n for (const client of failedClients) {\n this.clients.delete(client);\n }\n\n // Telemetry: Track broadcast completion\n await this.recordWebSocketEvent(\"ws.broadcast.complete\", {\n \"ws.broadcast.type\": messageType,\n \"ws.broadcast.success_count\": this.clients.size - failureCount,\n \"ws.broadcast.failure_count\": failureCount,\n });\n\n // Always warn about significant broadcast failures (>10% failure rate)\n if (failureCount > 0) {\n const failureRate = this.clients.size > 0 ? (failureCount / this.clients.size) * 100 : 0;\n if (failureRate >= 10 || failureCount > 5) {\n console.warn(\n `[WebSocket] Broadcast completed with ${failureCount}/${this.clients.size} client failures (${failureRate.toFixed(1)}% failure rate)`\n );\n }\n }\n }\n\n /**\n * Record WebSocket telemetry event\n * Creates spans for WebSocket operations when telemetry is enabled\n */\n private async recordWebSocketEvent(\n eventName: string,\n attributes: Record<string, any>\n ): Promise<void> {\n // Check if telemetry is enabled (compile-time constant set by esbuild)\n // @ts-ignore - TELEMETRY_ENABLED is a global constant defined at build time\n const isTelemetryEnabled = typeof TELEMETRY_ENABLED !== \"undefined\" ? TELEMETRY_ENABLED : false;\n\n if (!isTelemetryEnabled) {\n return;\n }\n\n try {\n // Dynamic import for tree-shaking\n const { startSpan, endSpan } = await import(\"../telemetry/index.js\");\n\n // Create span for WebSocket event\n const span = startSpan(eventName, attributes);\n\n // End span immediately (WebSocket events are typically instantaneous)\n endSpan(span);\n } catch (error) {\n // Log telemetry failures to enable production debugging\n // Always log errors (not just in DEBUG) to catch silent failures\n console.warn(`[Telemetry] Failed to record WebSocket event: ${getErrorMessage(error)}`);\n }\n }\n\n /**\n * Find an element across all layers\n */\n private async findElement(id: string): Promise<Element | null> {\n for (const layer of this.model.layers.values()) {\n const element = layer.getElement(id);\n if (element) return element;\n }\n return null;\n }\n\n /**\n * Handle WebSocket messages\n */\n private async handleWSMessage(ws: HonoWSContext, data: WSMessage): Promise<void> {\n const simpleMsg = data as SimpleWSMessage;\n switch (simpleMsg.type) {\n case \"subscribe\":\n // Send subscribed confirmation\n const topics = simpleMsg.topics || [\"model\", \"annotations\"];\n ws.send(\n JSON.stringify({\n type: \"subscribed\",\n topics,\n timestamp: new Date().toISOString(),\n })\n );\n\n // Send initial model state\n const modelData = await this.serializeModel();\n ws.send(\n JSON.stringify({\n type: \"model\",\n data: modelData,\n timestamp: new Date().toISOString(),\n })\n );\n break;\n\n case \"ping\":\n // Respond with pong\n ws.send(\n JSON.stringify({\n type: \"pong\",\n timestamp: new Date().toISOString(),\n })\n );\n break;\n\n case \"annotate\":\n if (simpleMsg.annotation) {\n await this.broadcastMessage({\n type: \"annotation\",\n data: simpleMsg.annotation,\n });\n }\n break;\n\n default:\n ws.send(\n JSON.stringify({\n type: \"error\",\n message: `Unknown message type: ${simpleMsg.type}`,\n })\n );\n }\n }\n\n /**\n * Handle JSON-RPC 2.0 messages for chat functionality\n */\n private async handleJSONRPCMessage(ws: HonoWSContext, data: WSMessage): Promise<void> {\n // Only requests have a method; responses have result/error\n if (!('method' in data)) {\n // This is a response, not something we handle in this function\n // Log unexpected protocol patterns for operational visibility\n const responseType = ('result' in data) ? 'success' : ('error' in data) ? 'error' : 'unknown';\n const msgId = ('id' in data) ? data.id : 'unknown';\n console.warn(\n `[WebSocket] Received JSON-RPC ${responseType} response from client (ID: ${msgId}), ignoring`\n );\n return;\n }\n const rpcMsg = data as JSONRPCRequest;\n const { method, params, id } = rpcMsg;\n\n // Helper to send JSON-RPC response\n const sendResponse = (result: any) => {\n ws.send(\n JSON.stringify({\n jsonrpc: \"2.0\",\n result,\n id,\n })\n );\n };\n\n // Helper to send JSON-RPC error\n const sendError = (code: number, message: string, data?: any) => {\n ws.send(\n JSON.stringify({\n jsonrpc: \"2.0\",\n error: { code, message, data },\n id,\n })\n );\n };\n\n try {\n switch (method) {\n case \"chat.status\":\n // Check if any chat client is available\n const hasClient = this.selectedChatClient !== undefined;\n\n // Determine error message: initialization error takes precedence\n let statusError: string | null = null;\n if (this.chatInitializationError) {\n statusError = `Chat initialization failed: ${this.chatInitializationError.message}`;\n } else if (!hasClient) {\n statusError = \"No chat client available. Install Claude Code or GitHub Copilot.\";\n }\n\n sendResponse({\n sdk_available: hasClient,\n sdk_version: hasClient ? this.selectedChatClient!.getClientName() : null,\n error_message: statusError,\n });\n break;\n\n case \"chat.send\":\n // Validate params\n if (!params || typeof params !== 'object' || !('message' in params) || typeof (params as any).message !== 'string' || (params as any).message.trim() === '') {\n sendError(JSONRPC_ERRORS.INVALID_PARAMS, \"Message cannot be empty\");\n return;\n }\n\n // Check if chat client is available\n if (!this.selectedChatClient) {\n sendError(\n JSONRPC_ERRORS.NO_CLIENT_AVAILABLE,\n \"No chat client available. Install Claude Code or GitHub Copilot to enable chat.\"\n );\n return;\n }\n\n // Generate conversation ID\n const conversationId = `conv-${++this.chatConversationCounter}-${Date.now()}`;\n\n // Launch chat with selected client\n await this.launchChat(ws, conversationId, (params as any).message, id);\n break;\n\n case \"chat.cancel\":\n // Find and cancel any active conversation\n let cancelled = false;\n let cancelledConvId = null;\n\n for (const [convId, process] of this.activeChatProcesses) {\n try {\n process.kill();\n this.activeChatProcesses.delete(convId);\n cancelled = true;\n cancelledConvId = convId;\n break;\n } catch (error) {\n console.warn(`[Chat] Failed to cancel process for conversation ${convId}: ${getErrorMessage(error)}`);\n }\n }\n\n sendResponse({\n cancelled,\n conversation_id: cancelledConvId,\n });\n break;\n\n default:\n sendError(JSONRPC_ERRORS.METHOD_NOT_FOUND, `Method not found: ${method}`);\n }\n } catch (error) {\n const errorMsg = getErrorMessage(error);\n sendError(JSONRPC_ERRORS.INTERNAL_ERROR, \"Internal error\", errorMsg);\n }\n }\n\n /**\n * Launch chat with selected client and stream responses via WebSocket\n */\n private async launchChat(\n ws: HonoWSContext,\n conversationId: string,\n message: string,\n requestId: string | number | undefined\n ): Promise<void> {\n if (!this.selectedChatClient) {\n ws.send(\n JSON.stringify({\n jsonrpc: \"2.0\",\n error: {\n code: JSONRPC_ERRORS.NO_CLIENT_AVAILABLE,\n message: \"No chat client available\",\n },\n id: requestId,\n })\n );\n return;\n }\n\n // Route to appropriate client-specific handler\n if (this.selectedChatClient instanceof ClaudeCodeClient) {\n await this.launchClaudeCodeChat(ws, conversationId, message, requestId);\n } else if (this.selectedChatClient instanceof CopilotClient) {\n await this.launchCopilotChat(ws, conversationId, message, requestId);\n } else {\n ws.send(\n JSON.stringify({\n jsonrpc: \"2.0\",\n error: {\n code: JSONRPC_ERRORS.NO_CLIENT_AVAILABLE,\n message: \"Unknown chat client type\",\n },\n id: requestId,\n })\n );\n }\n }\n\n /**\n * Launch Claude Code CLI with dr-architect agent and stream responses\n */\n private async launchClaudeCodeChat(\n ws: HonoWSContext,\n conversationId: string,\n message: string,\n requestId: string | number | undefined\n ): Promise<void> {\n try {\n // Build command arguments\n const cmd = [\"claude\", \"--agent\", \"dr-architect\", \"--print\"];\n\n // Add dangerously-skip-permissions flag if withDanger is enabled\n if (this.withDanger) {\n cmd.push(\"--dangerously-skip-permissions\");\n }\n\n cmd.push(\"--verbose\", \"--output-format\", \"stream-json\");\n\n // Launch claude with dr-architect agent for comprehensive DR expertise\n const proc = spawn(cmd[0], cmd.slice(1), {\n cwd: this.model.rootPath,\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n });\n\n // Node reports spawn failures (e.g. ENOENT if `claude` isn't on PATH) asynchronously\n // via an 'error' event rather than a synchronous throw. Without this handler, an\n // unhandled 'error' event on the ChildProcess EventEmitter throws and crashes the\n // whole server (server-entry.ts's uncaughtException handler tears the process down)\n // instead of just failing this one chat request.\n proc.on(\"error\", (err) => {\n const errorMsg = getErrorMessage(err);\n console.error(\n `[Claude Code] Failed to spawn subprocess for conversation ${conversationId}: ${errorMsg}`\n );\n this.activeChatProcesses.delete(conversationId);\n try {\n ws.send(\n JSON.stringify({\n jsonrpc: \"2.0\",\n error: {\n code: JSONRPC_ERRORS.INTERNAL_ERROR,\n message: `Failed to launch Claude Code: ${errorMsg}`,\n },\n id: requestId,\n })\n );\n } catch (sendError) {\n console.error(\n `[Claude Code] Failed to send spawn-error notification: ${getErrorMessage(sendError)}`\n );\n }\n });\n // A failed spawn also destroys the stdio streams Node already created, which can\n // raise a second, separate unhandled 'error' event on `proc.stdin` itself. Swallow\n // it here since the handler above already reports the failure to the client.\n proc.stdin?.on(\"error\", () => {});\n\n // Store active process\n this.activeChatProcesses.set(conversationId, proc);\n\n // Send user message via stdin (like Python prototype)\n proc.stdin?.write(message);\n proc.stdin?.end();\n\n // Stream stdout (JSON events)\n const decoder = new TextDecoder();\n\n const streamOutput = async () => {\n let accumulatedText = \"\";\n let buffer = \"\";\n\n try {\n for await (const chunkBuf of proc.stdout!) {\n const chunk = decoder.decode(chunkBuf, { stream: true });\n buffer += chunk;\n\n // Process complete lines (JSON events)\n const lines = buffer.split(\"\\n\");\n buffer = lines.pop() || \"\";\n\n for (const line of lines) {\n if (!line.trim()) continue;\n\n try {\n const event = JSON.parse(line);\n\n if (event.type === \"assistant\") {\n // Extract content blocks from assistant message\n const content = event.message?.content || [];\n for (const block of content) {\n if (block.type === \"text\") {\n accumulatedText += block.text;\n // Send text chunk notification\n ws.send(\n JSON.stringify({\n jsonrpc: \"2.0\",\n method: \"chat.response.chunk\",\n params: {\n conversation_id: conversationId,\n content: block.text,\n is_final: false,\n timestamp: new Date().toISOString(),\n },\n })\n );\n } else if (block.type === \"tool_use\") {\n // Send tool invocation notification\n ws.send(\n JSON.stringify({\n jsonrpc: \"2.0\",\n method: \"chat.tool.invoke\",\n params: {\n conversation_id: conversationId,\n tool_name: block.name,\n tool_input: block.input,\n timestamp: new Date().toISOString(),\n },\n })\n );\n }\n }\n } else if (event.type === \"result\") {\n // Tool result\n ws.send(\n JSON.stringify({\n jsonrpc: \"2.0\",\n method: \"chat.tool.result\",\n params: {\n conversation_id: conversationId,\n result: event.result,\n timestamp: new Date().toISOString(),\n },\n })\n );\n }\n } catch (parseError) {\n // Non-JSON line, send as raw text chunk\n if (process.env.DEBUG) {\n console.debug(`[Chat] Failed to parse JSON chunk: ${getErrorMessage(parseError)}`);\n }\n ws.send(\n JSON.stringify({\n jsonrpc: \"2.0\",\n method: \"chat.response.chunk\",\n params: {\n conversation_id: conversationId,\n content: line + \"\\n\",\n is_final: false,\n timestamp: new Date().toISOString(),\n },\n })\n );\n }\n }\n }\n\n // Process remaining buffer\n if (buffer.trim()) {\n ws.send(\n JSON.stringify({\n jsonrpc: \"2.0\",\n method: \"chat.response.chunk\",\n params: {\n conversation_id: conversationId,\n content: buffer,\n is_final: true,\n timestamp: new Date().toISOString(),\n },\n })\n );\n }\n\n // Wait for process to complete\n const exitCode = await this.waitForExit(proc);\n\n // Clean up\n this.activeChatProcesses.delete(conversationId);\n\n // Send completion response\n ws.send(\n JSON.stringify({\n jsonrpc: \"2.0\",\n result: {\n conversation_id: conversationId,\n status: exitCode === 0 ? \"complete\" : \"error\",\n exit_code: exitCode,\n full_response: accumulatedText,\n timestamp: new Date().toISOString(),\n },\n id: requestId,\n })\n );\n } catch (error) {\n // A process that never actually spawned (see the proc.on(\"error\", ...)\n // handler above, which already reported this failure to the client) has no\n // pid - Node assigns one synchronously only once posix_spawn succeeds. Bail\n // out here instead of sending a second, duplicate JSON-RPC response for the\n // same request or trying to kill a process that never started.\n if (!proc.pid) {\n return;\n }\n\n const errorMsg = getErrorMessage(error);\n\n ws.send(\n JSON.stringify({\n jsonrpc: \"2.0\",\n error: {\n code: JSONRPC_ERRORS.INTERNAL_ERROR,\n message: `Chat failed: ${errorMsg}`,\n },\n id: requestId,\n })\n );\n\n this.activeChatProcesses.delete(conversationId);\n\n try {\n await this.killProcessWithRetry(proc, conversationId, \"Claude Code\");\n } catch (error) {\n console.error(`[Claude Code] Failed to kill process for conversation ${conversationId}: ${getErrorMessage(error)}`);\n }\n }\n };\n\n // Start streaming in background\n streamOutput().catch((err) => {\n const errorMsg = getErrorMessage(err);\n console.error(`[Claude Code] Stream error: ${errorMsg}`);\n\n // Notify client of stream failure via WebSocket\n try {\n ws.send(\n JSON.stringify({\n jsonrpc: \"2.0\",\n error: {\n code: JSONRPC_ERRORS.INTERNAL_ERROR,\n message: `Stream error: ${errorMsg}`,\n },\n id: requestId,\n })\n );\n } catch (sendError) {\n console.error(`[Claude Code] Failed to send stream error notification: ${getErrorMessage(sendError)}`);\n }\n });\n } catch (error) {\n const errorMsg = getErrorMessage(error);\n\n ws.send(\n JSON.stringify({\n jsonrpc: \"2.0\",\n error: {\n code: JSONRPC_ERRORS.INTERNAL_ERROR,\n message: `Failed to launch Claude Code: ${errorMsg}`,\n },\n id: requestId,\n })\n );\n }\n }\n\n /**\n * Launch GitHub Copilot CLI and stream responses via WebSocket\n */\n private async launchCopilotChat(\n ws: HonoWSContext,\n conversationId: string,\n message: string,\n requestId: string | number | undefined\n ): Promise<void> {\n try {\n // Determine which command to use (gh copilot or standalone copilot)\n let cmd: string[];\n\n // Check if gh CLI with copilot extension is available\n const ghResult = spawnSync(\"gh\", [\"copilot\", \"--version\"], {\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n\n // Log why we're falling back to standalone copilot, for debuggability: `gh`\n // missing (ENOENT), `gh` present but the copilot extension isn't installed\n // (non-zero exit), or another spawn failure are otherwise indistinguishable.\n if (ghResult.error) {\n console.warn(\n `[Copilot] 'gh copilot --version' probe failed to spawn: ${getErrorMessage(ghResult.error)}`\n );\n } else if (ghResult.status !== 0 && process.env.VERBOSE) {\n console.log(\n `[Copilot] 'gh copilot' unavailable (exit ${ghResult.status}), falling back to standalone 'copilot'`\n );\n }\n\n if (ghResult.status === 0) {\n cmd = [\"gh\", \"copilot\", \"explain\"];\n\n // Add allow-all-tools flag if withDanger is enabled\n if (this.withDanger) {\n cmd.push(\"--allow-all-tools\");\n }\n\n cmd.push(message);\n } else {\n // Try standalone copilot\n cmd = [\"copilot\", \"explain\"];\n\n // Add allow-all-tools flag if withDanger is enabled\n if (this.withDanger) {\n cmd.push(\"--allow-all-tools\");\n }\n\n cmd.push(message);\n }\n\n // Launch GitHub Copilot\n const proc = spawn(cmd[0], cmd.slice(1), {\n cwd: this.model.rootPath,\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n });\n\n // See the matching handler in launchClaudeCodeChat: Node reports spawn failures\n // (e.g. ENOENT if `gh`/`copilot` isn't on PATH) asynchronously via an 'error'\n // event, and an unhandled one would crash the whole server instead of just this\n // one chat request.\n proc.on(\"error\", (err) => {\n const errorMsg = getErrorMessage(err);\n console.error(\n `[Copilot] Failed to spawn subprocess for conversation ${conversationId}: ${errorMsg}`\n );\n this.activeChatProcesses.delete(conversationId);\n try {\n ws.send(\n JSON.stringify({\n jsonrpc: \"2.0\",\n error: {\n code: JSONRPC_ERRORS.INTERNAL_ERROR,\n message: `Failed to launch GitHub Copilot: ${errorMsg}`,\n },\n id: requestId,\n })\n );\n } catch (sendError) {\n console.error(\n `[Copilot] Failed to send spawn-error notification: ${getErrorMessage(sendError)}`\n );\n }\n });\n // See the matching guard in launchClaudeCodeChat: a failed spawn can also raise a\n // separate unhandled 'error' event on the stdin stream Node already created.\n proc.stdin?.on(\"error\", () => {});\n\n // Store active process\n this.activeChatProcesses.set(conversationId, proc);\n\n // GitHub Copilot outputs plain text/markdown (not JSON)\n const decoder = new TextDecoder();\n\n const streamOutput = async () => {\n let accumulatedText = \"\";\n\n try {\n for await (const chunkBuf of proc.stdout!) {\n const chunk = decoder.decode(chunkBuf, { stream: true });\n accumulatedText += chunk;\n\n // Send text chunk as it arrives\n ws.send(\n JSON.stringify({\n jsonrpc: \"2.0\",\n method: \"chat.response.chunk\",\n params: {\n conversation_id: conversationId,\n content: chunk,\n is_final: false,\n timestamp: new Date().toISOString(),\n },\n })\n );\n }\n\n // Wait for process to complete\n const exitCode = await this.waitForExit(proc);\n\n // Clean up\n this.activeChatProcesses.delete(conversationId);\n\n // Send completion response\n ws.send(\n JSON.stringify({\n jsonrpc: \"2.0\",\n result: {\n conversation_id: conversationId,\n status: exitCode === 0 ? \"complete\" : \"error\",\n exit_code: exitCode,\n full_response: accumulatedText,\n timestamp: new Date().toISOString(),\n },\n id: requestId,\n })\n );\n } catch (error) {\n // See the matching guard in launchClaudeCodeChat: a process that never\n // actually spawned has no pid, and its own proc.on(\"error\", ...) handler\n // above already reported the failure - avoid a duplicate response/kill.\n if (!proc.pid) {\n return;\n }\n\n const errorMsg = getErrorMessage(error);\n\n ws.send(\n JSON.stringify({\n jsonrpc: \"2.0\",\n error: {\n code: JSONRPC_ERRORS.INTERNAL_ERROR,\n message: `Chat failed: ${errorMsg}`,\n },\n id: requestId,\n })\n );\n\n this.activeChatProcesses.delete(conversationId);\n\n try {\n await this.killProcessWithRetry(proc, conversationId, \"Copilot\");\n } catch (error) {\n console.error(`[Copilot] Failed to kill process for conversation ${conversationId}: ${getErrorMessage(error)}`);\n }\n }\n };\n\n // Start streaming in background\n streamOutput().catch((err) => {\n console.error(`[Copilot] Stream error: ${getErrorMessage(err)}`);\n });\n } catch (error) {\n const errorMsg = getErrorMessage(error);\n\n ws.send(\n JSON.stringify({\n jsonrpc: \"2.0\",\n error: {\n code: JSONRPC_ERRORS.INTERNAL_ERROR,\n message: `Failed to launch GitHub Copilot: ${errorMsg}`,\n },\n id: requestId,\n })\n );\n }\n }\n\n /**\n * Setup file watcher for model changes\n */\n private setupFileWatcher(): void {\n const modelPath = `${this.model.rootPath}/documentation-robotics/model`;\n\n // Use chokidar for consistent, well-tested cross-platform recursive watching\n // rather than Node's built-in recursive fs.watch (Linux support for the\n // `recursive` option only landed in Node 19.1.0, and its maturity/event\n // semantics still vary more by platform than chokidar's).\n try {\n this.watcher = chokidar.watch(modelPath, {\n persistent: true,\n ignoreInitial: true,\n });\n\n this.watcher.on(\"all\", async (_event: string, path: string) => {\n if (process.env.VERBOSE) {\n console.log(`[Watcher] File changed: ${path}`);\n }\n\n try {\n // Reload model and changesets\n this.model = await Model.load(this.model.rootPath, { lazyLoad: false });\n await this.loadChangesetsFromDisk();\n\n // Broadcast update to all clients\n const modelData = await this.serializeModel();\n const message = JSON.stringify({\n type: \"model.updated\",\n data: modelData,\n timestamp: new Date().toISOString(),\n });\n\n for (const client of this.clients) {\n try {\n client.send(message);\n } catch (error) {\n const msg = getErrorMessage(error);\n console.warn(`[Watcher] Failed to send update: ${msg}`);\n }\n }\n } catch (error) {\n const message = getErrorMessage(error);\n console.error(`[Watcher] Failed to reload model: ${message}`);\n }\n });\n\n this.watcher.on(\"error\", (err: unknown) => {\n console.warn(`[Watcher] Could not watch ${modelPath}: ${getErrorMessage(err)}`);\n });\n } catch (err) {\n console.warn(`[Watcher] Could not watch ${modelPath}: ${err}`);\n }\n }\n\n /**\n * Load all JSON schemas from bundled directory (recursive)\n * Loads schemas from all subdirectories: base/, nodes/, relationships/, etc.\n */\n private async loadSchemas(): Promise<Record<string, any>> {\n const schemasPath = new URL(\"../schemas/bundled/\", import.meta.url).pathname;\n const schemas: Record<string, any> = {};\n\n try {\n const fs = await import(\"fs/promises\");\n const path = await import(\"path\");\n\n // Recursive function to walk through directories\n const walkDirectory = async (dir: string, prefix: string = \"\"): Promise<void> => {\n const entries = await fs.readdir(dir, { withFileTypes: true });\n\n for (const entry of entries) {\n const fullPath = path.join(dir, entry.name);\n const schemaKey = prefix ? `${prefix}/${entry.name}` : entry.name;\n\n if (entry.isDirectory()) {\n // Recursively walk subdirectories\n await walkDirectory(fullPath, schemaKey);\n } else if (entry.isFile() && (entry.name.endsWith(\".schema.json\") || entry.name.endsWith(\".json\"))) {\n // Load JSON schema files\n const content = await fs.readFile(fullPath, \"utf-8\");\n try {\n schemas[schemaKey] = JSON.parse(content);\n } catch (parseError) {\n const parseErrorMsg = parseError instanceof Error ? parseError.message : String(parseError);\n throw new Error(`Invalid JSON in schema file ${fullPath}: ${parseErrorMsg}`);\n }\n }\n }\n };\n\n await walkDirectory(schemasPath);\n\n return schemas;\n } catch (error) {\n const errorMsg = getErrorMessage(error);\n console.error(\"Failed to load schemas:\", error);\n throw new Error(`Failed to load schema files: ${errorMsg}`);\n }\n }\n\n /**\n * Get viewer HTML\n */\n private getViewerHTML(): string {\n return `<!DOCTYPE html>\n<html>\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>Documentation Robotics Viewer</title>\n <style>\n * {\n margin: 0;\n padding: 0;\n box-sizing: border-box;\n }\n\n body {\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;\n background: #f5f5f5;\n color: #333;\n }\n\n header {\n background: white;\n border-bottom: 1px solid #e0e0e0;\n padding: 20px;\n box-shadow: 0 2px 4px rgba(0,0,0,0.1);\n }\n\n .header-content {\n max-width: 1400px;\n margin: 0 auto;\n display: flex;\n justify-content: space-between;\n align-items: center;\n }\n\n h1 {\n font-size: 24px;\n font-weight: 600;\n }\n\n .status-badge {\n display: inline-block;\n padding: 6px 12px;\n border-radius: 4px;\n font-size: 12px;\n font-weight: 500;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n }\n\n .status-badge.connected {\n background: #4caf50;\n color: white;\n }\n\n .status-badge.disconnected {\n background: #f44336;\n color: white;\n }\n\n .container {\n max-width: 1400px;\n margin: 0 auto;\n padding: 20px;\n }\n\n .content {\n display: grid;\n grid-template-columns: 300px 1fr;\n gap: 20px;\n height: calc(100vh - 100px);\n }\n\n .sidebar {\n background: white;\n border-radius: 4px;\n overflow-y: auto;\n box-shadow: 0 2px 4px rgba(0,0,0,0.1);\n padding: 16px;\n }\n\n .main-panel {\n background: white;\n border-radius: 4px;\n overflow-y: auto;\n box-shadow: 0 2px 4px rgba(0,0,0,0.1);\n padding: 20px;\n }\n\n .layer-group {\n margin-bottom: 16px;\n }\n\n .layer-header {\n font-weight: 600;\n padding: 8px 0;\n border-bottom: 2px solid #e0e0e0;\n margin-bottom: 8px;\n cursor: pointer;\n user-select: none;\n display: flex;\n align-items: center;\n }\n\n .layer-header:hover {\n color: #2196F3;\n }\n\n .layer-header::before {\n content: '\u25B6';\n margin-right: 8px;\n display: inline-block;\n transition: transform 0.2s;\n }\n\n .layer-header.expanded::before {\n transform: rotate(90deg);\n }\n\n .layer-elements {\n margin-left: 16px;\n display: none;\n }\n\n .layer-elements.expanded {\n display: block;\n }\n\n .element-item {\n padding: 8px;\n margin-bottom: 4px;\n background: #f9f9f9;\n border-left: 3px solid #e0e0e0;\n cursor: pointer;\n border-radius: 2px;\n transition: all 0.2s;\n font-size: 13px;\n }\n\n .element-item:hover {\n background: #f0f0f0;\n border-left-color: #2196F3;\n }\n\n .element-item.selected {\n background: #e3f2fd;\n border-left-color: #2196F3;\n font-weight: 500;\n }\n\n .element-details {\n min-height: 300px;\n }\n\n .detail-header {\n border-bottom: 2px solid #e0e0e0;\n padding-bottom: 12px;\n margin-bottom: 16px;\n }\n\n .detail-header h2 {\n font-size: 18px;\n margin-bottom: 4px;\n }\n\n .detail-header .element-type {\n font-size: 12px;\n color: #999;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n }\n\n .detail-section {\n margin-bottom: 16px;\n }\n\n .detail-section h3 {\n font-size: 13px;\n font-weight: 600;\n text-transform: uppercase;\n color: #999;\n margin-bottom: 8px;\n letter-spacing: 0.5px;\n }\n\n .detail-value {\n font-size: 14px;\n line-height: 1.6;\n color: #333;\n word-break: break-word;\n }\n\n .annotations-list {\n max-height: 200px;\n overflow-y: auto;\n border: 1px solid #e0e0e0;\n border-radius: 4px;\n padding: 8px;\n }\n\n .annotation-item {\n padding: 8px;\n margin-bottom: 8px;\n background: #fffde7;\n border-left: 3px solid #fbc02d;\n border-radius: 2px;\n font-size: 12px;\n }\n\n .annotation-item .author {\n font-weight: 600;\n color: #666;\n }\n\n .annotation-item .text {\n margin: 4px 0 0 0;\n color: #333;\n }\n\n .annotation-item .timestamp {\n font-size: 11px;\n color: #999;\n margin-top: 4px;\n }\n\n .annotation-form {\n display: grid;\n gap: 8px;\n margin-top: 12px;\n }\n\n .annotation-form input,\n .annotation-form textarea {\n padding: 8px;\n border: 1px solid #e0e0e0;\n border-radius: 4px;\n font-family: inherit;\n font-size: 12px;\n }\n\n .annotation-form textarea {\n resize: vertical;\n min-height: 60px;\n }\n\n .annotation-form button {\n padding: 8px 12px;\n background: #2196F3;\n color: white;\n border: none;\n border-radius: 4px;\n font-weight: 500;\n cursor: pointer;\n transition: background 0.2s;\n }\n\n .annotation-form button:hover {\n background: #1976D2;\n }\n\n .loading {\n text-align: center;\n padding: 40px;\n color: #999;\n }\n\n .loading::after {\n content: '';\n display: inline-block;\n width: 16px;\n height: 16px;\n border: 2px solid #f3f3f3;\n border-top: 2px solid #2196F3;\n border-radius: 50%;\n animation: spin 1s linear infinite;\n margin-left: 8px;\n }\n\n @keyframes spin {\n 0% { transform: rotate(0deg); }\n 100% { transform: rotate(360deg); }\n }\n\n .stats {\n font-size: 12px;\n color: #999;\n margin-bottom: 16px;\n padding-bottom: 12px;\n border-bottom: 1px solid #e0e0e0;\n }\n </style>\n</head>\n<body>\n <header>\n <div class=\"header-content\">\n <div>\n <h1>\uD83D\uDCDA Documentation Robotics Viewer</h1>\n </div>\n <div class=\"status-badge connected\" id=\"status\">\u25CF Connected</div>\n </div>\n </header>\n\n <div class=\"container\">\n <div class=\"content\">\n <div class=\"sidebar\">\n <div class=\"stats\" id=\"stats\">Loading...</div>\n <div id=\"model-tree\">\n <div class=\"loading\">Loading model...</div>\n </div>\n </div>\n\n <div class=\"main-panel\">\n <div id=\"element-details\">\n <div class=\"loading\">Select an element to view details</div>\n </div>\n </div>\n </div>\n </div>\n\n <script>\n let currentModel = null;\n let selectedElementId = null;\n\n const statusEl = document.getElementById('status');\n const modelTreeEl = document.getElementById('model-tree');\n const detailsEl = document.getElementById('element-details');\n const statsEl = document.getElementById('stats');\n\n /**\n * Escape HTML special characters to prevent XSS\n * @param {string} text - Text to escape\n * @returns {string} HTML-escaped text\n */\n function escapeHtml(text) {\n if (!text) return '';\n const div = document.createElement('div');\n div.textContent = text;\n return div.innerHTML;\n }\n\n // Extract token from URL query parameters if present\n const urlParams = new URLSearchParams(window.location.search);\n const token = urlParams.get('token');\n\n // Log token status for debugging\n if (!token) {\n console.warn('[WebSocket] No authentication token found in URL. If authentication is enabled, connection will fail.');\n console.warn('[WebSocket] Expected URL format: http://localhost:PORT?token=YOUR_TOKEN');\n } else {\n console.log('[WebSocket] Found authentication token in URL');\n }\n\n // Build WebSocket URL with token if available\n let wsUrl = \\`ws://\\${window.location.host}/ws\\`;\n if (token) {\n wsUrl += \\`?token=\\${encodeURIComponent(token)}\\`;\n }\n\n console.log('[WebSocket] Connecting to:', wsUrl.replace(/token=[^&]+/, 'token=***'));\n\n const ws = new WebSocket(wsUrl);\n\n ws.addEventListener('open', () => {\n console.log('WebSocket connected');\n updateStatus(true);\n ws.send(JSON.stringify({ type: 'subscribe' }));\n });\n\n ws.addEventListener('message', (event) => {\n try {\n const message = JSON.parse(event.data);\n\n if (message.type === 'model' || message.type === 'model-update') {\n currentModel = message.data;\n renderModel();\n } else if (message.type === 'annotation') {\n // Update annotations for the element\n renderSelectedElement();\n } else if (message.type === 'error') {\n console.error('WebSocket error:', message.message);\n }\n } catch (error) {\n console.error('Failed to parse message:', error);\n }\n });\n\n ws.addEventListener('close', (event) => {\n console.log('WebSocket disconnected', event.code, event.reason);\n updateStatus(false);\n\n // If closed immediately with auth error codes, show helpful message\n if (!token && (event.code === 1002 || event.code === 1008 || event.code === 1006)) {\n showAuthError();\n }\n });\n\n ws.addEventListener('error', (error) => {\n console.error('WebSocket error:', error);\n updateStatus(false);\n\n // If we don't have a token, likely an auth issue\n if (!token) {\n showAuthError();\n }\n });\n\n function showAuthError() {\n const errorDiv = document.createElement('div');\n errorDiv.style.cssText = 'position: fixed; top: 80px; left: 50%; transform: translateX(-50%); background: #f44336; color: white; padding: 16px 24px; border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.3); z-index: 10000; max-width: 500px; text-align: center;';\n errorDiv.innerHTML = \\`\n <strong>\u26A0\uFE0F Authentication Required</strong><br>\n <span style=\"font-size: 14px; margin-top: 8px; display: inline-block;\">\n Please use the full URL with authentication token from the terminal.\n </span>\n \\`;\n document.body.appendChild(errorDiv);\n\n // Auto-dismiss after 10 seconds\n setTimeout(() => {\n errorDiv.style.transition = 'opacity 0.5s';\n errorDiv.style.opacity = '0';\n setTimeout(() => errorDiv.remove(), 500);\n }, 10000);\n }\n\n function updateStatus(connected) {\n if (connected) {\n statusEl.className = 'status-badge connected';\n statusEl.textContent = '\u25CF Connected';\n } else {\n statusEl.className = 'status-badge disconnected';\n statusEl.textContent = '\u25CF Disconnected';\n }\n }\n\n function renderModel() {\n if (!currentModel) return;\n\n // Update stats\n const layerCount = Object.keys(currentModel.layers).length;\n const elementCount = currentModel.totalElements || 0;\n statsEl.textContent = \\`\\${layerCount} layers \u00B7 \\${elementCount} elements\\`;\n\n // Render layers and elements\n modelTreeEl.innerHTML = '';\n const layers = Object.entries(currentModel.layers)\n .sort((a, b) => a[0].localeCompare(b[0]));\n\n for (const [layerName, layerData] of layers) {\n const layerDiv = document.createElement('div');\n layerDiv.className = 'layer-group';\n\n const headerDiv = document.createElement('div');\n headerDiv.className = 'layer-header';\n headerDiv.textContent = layerName;\n headerDiv.addEventListener('click', () => {\n headerDiv.classList.toggle('expanded');\n elementsDiv.classList.toggle('expanded');\n });\n\n const elementsDiv = document.createElement('div');\n elementsDiv.className = 'layer-elements';\n\n for (const element of layerData.elements) {\n const elemDiv = document.createElement('div');\n elemDiv.className = 'element-item';\n const elementSlug = element.path || element.id;\n if (elementSlug === selectedElementId) {\n elemDiv.classList.add('selected');\n }\n elemDiv.innerHTML = \\`\\${escapeHtml(elementSlug)}<br><span style=\"font-size: 11px; color: #999;\">\\${escapeHtml(element.name)}</span>\\`;\n elemDiv.addEventListener('click', () => {\n selectedElementId = elementSlug;\n document.querySelectorAll('.element-item').forEach(el => el.classList.remove('selected'));\n elemDiv.classList.add('selected');\n renderSelectedElement();\n });\n\n elementsDiv.appendChild(elemDiv);\n }\n\n layerDiv.appendChild(headerDiv);\n layerDiv.appendChild(elementsDiv);\n modelTreeEl.appendChild(layerDiv);\n }\n }\n\n function renderSelectedElement() {\n if (!selectedElementId || !currentModel) {\n detailsEl.innerHTML = '<div class=\"loading\">Select an element to view details</div>';\n return;\n }\n\n // Find element in model\n let element = null;\n for (const layer of Object.values(currentModel.layers)) {\n element = layer.elements.find(e => (e.path || e.id) === selectedElementId);\n if (element) break;\n }\n\n if (!element) {\n detailsEl.innerHTML = '<div class=\"loading\">Element not found</div>';\n return;\n }\n\n let html = \\`\n <div class=\"detail-header\">\n <div class=\"element-type\">\\${escapeHtml(element.type)}</div>\n <h2>\\${escapeHtml(element.name)}</h2>\n <div style=\"font-size: 12px; color: #666; margin-top: 4px;\"><code>\\${escapeHtml(element.path || element.id)}</code></div>\n </div>\n \\`;\n\n if (element.description) {\n html += \\`\n <div class=\"detail-section\">\n <h3>Description</h3>\n <div class=\"detail-value\">\\${escapeHtml(element.description)}</div>\n </div>\n \\`;\n }\n\n if (element.properties && Object.keys(element.properties).length > 0) {\n html += \\`\n <div class=\"detail-section\">\n <h3>Properties</h3>\n <div class=\"detail-value\"><pre>\\${escapeHtml(JSON.stringify(element.properties, null, 2))}</pre></div>\n </div>\n \\`;\n }\n\n if (element.annotations && element.annotations.length > 0) {\n html += \\`\n <div class=\"detail-section\">\n <h3>Annotations</h3>\n <div class=\"annotations-list\">\n \\${element.annotations.map(ann => \\`\n <div class=\"annotation-item\">\n <div class=\"author\">\\${escapeHtml(ann.author)}</div>\n <div class=\"text\">\\${escapeHtml(ann.text)}</div>\n <div class=\"timestamp\">\\${new Date(ann.timestamp).toLocaleString()}</div>\n </div>\n \\`).join('')}\n </div>\n </div>\n \\`;\n }\n\n html += \\`\n <div class=\"detail-section\">\n <h3>Add Annotation</h3>\n <form class=\"annotation-form\" onsubmit=\"addAnnotation(event, '\\${escapeHtml(element.path || element.id)}')\">\n <input type=\"text\" placeholder=\"Author name\" id=\"ann-author\" required>\n <textarea placeholder=\"Annotation text\" id=\"ann-text\" required></textarea>\n <button type=\"submit\">Add Annotation</button>\n </form>\n <div id=\"ann-error\" style=\"margin-top: 8px; min-height: 1.2em;\"></div>\n </div>\n \\`;\n\n detailsEl.innerHTML = html;\n }\n\n function addAnnotation(event, elementId) {\n event.preventDefault();\n\n const author = document.getElementById('ann-author').value;\n const text = document.getElementById('ann-text').value;\n\n if (!author || !text) return;\n\n // Send to server using new annotation API\n fetch('/api/annotations', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ elementId, author, content: text })\n })\n .then(r => r.json().then(data => ({ ok: r.ok, data })))\n .then(({ ok, data }) => {\n if (ok && data && data.id) {\n document.getElementById('ann-author').value = '';\n document.getElementById('ann-text').value = '';\n const errorDiv = document.getElementById('ann-error');\n if (errorDiv) errorDiv.textContent = '';\n } else if (!ok) {\n const errorMsg = (data && data.error) ? data.error : 'Failed to create annotation';\n const errorDiv = document.getElementById('ann-error');\n if (errorDiv) {\n errorDiv.textContent = 'Error: ' + errorMsg;\n errorDiv.style.color = '#d32f2f';\n errorDiv.style.marginTop = '8px';\n }\n }\n })\n .catch(err => {\n const errorDiv = document.getElementById('ann-error');\n if (errorDiv) {\n errorDiv.textContent = 'Error: Failed to add annotation: ' + err.message;\n errorDiv.style.color = '#d32f2f';\n errorDiv.style.marginTop = '8px';\n }\n });\n }\n </script>\n</body>\n</html>`;\n }\n\n /**\n * Serve a file from the custom viewer path\n */\n private async serveCustomViewer(filePath: string): Promise<Response> {\n if (process.env.VERBOSE) console.log(`[VIEWER] serveCustomViewer called for: ${filePath}`);\n\n if (!this.viewerPath) {\n console.error(`[VIEWER] Custom viewer path not configured`);\n return new Response(\"Custom viewer path not configured\", { status: 500 });\n }\n\n try {\n const path = await import(\"path\");\n const fs = await import(\"fs/promises\");\n\n // Resolve absolute path and prevent directory traversal\n const fullPath = path.resolve(this.viewerPath, filePath);\n if (process.env.VERBOSE) console.log(`[VIEWER] Resolved path: ${fullPath}`);\n\n // Security check: ensure the resolved path is within viewerPath\n if (!fullPath.startsWith(path.resolve(this.viewerPath))) {\n console.error(`[VIEWER] Security check failed - path outside viewer directory`);\n return new Response(\"Forbidden\", { status: 403 });\n }\n\n // Read file\n if (process.env.VERBOSE) console.log(`[VIEWER] Reading file: ${fullPath}`);\n const content = await fs.readFile(fullPath);\n\n // Determine content type\n const ext = path.extname(fullPath).toLowerCase();\n const contentTypes: Record<string, string> = {\n \".html\": \"text/html\",\n \".css\": \"text/css\",\n \".js\": \"application/javascript\",\n \".json\": \"application/json\",\n \".png\": \"image/png\",\n \".jpg\": \"image/jpeg\",\n \".jpeg\": \"image/jpeg\",\n \".gif\": \"image/gif\",\n \".svg\": \"image/svg+xml\",\n \".ico\": \"image/x-icon\",\n \".woff\": \"font/woff\",\n \".woff2\": \"font/woff2\",\n \".ttf\": \"font/ttf\",\n \".eot\": \"application/vnd.ms-fontobject\",\n };\n\n const contentType = contentTypes[ext] || \"application/octet-stream\";\n\n if (process.env.VERBOSE)\n console.log(`[VIEWER] Successfully read file, returning with Content-Type: ${contentType}`);\n return new Response(content, {\n status: 200,\n headers: {\n \"Content-Type\": contentType,\n },\n });\n } catch (error) {\n if ((error as any).code === \"ENOENT\") {\n console.error(`[VIEWER] File not found: ${filePath}`);\n return new Response(\"File not found\", { status: 404 });\n }\n console.error(`[VIEWER] Error serving custom viewer file ${filePath}:`, error);\n return new Response(\"Internal server error\", { status: 500 });\n }\n }\n\n /**\n * Load all changesets from disk into the in-memory map\n */\n private async loadChangesetsFromDisk(): Promise<void> {\n if (!this.changesetStorage) return;\n try {\n const changesets = await this.changesetStorage.list();\n this.changesets.clear();\n for (const cs of changesets) {\n this.changesets.set(cs.id, cs);\n }\n if (process.env.VERBOSE) {\n console.log(`[Changesets] Loaded ${changesets.length} changeset(s) from disk`);\n }\n } catch (err) {\n console.warn(`[Changesets] Warning: Failed to load changesets from disk: ${err}`);\n }\n }\n\n /**\n * Start the server\n */\n async start(port: number = 8080): Promise<void> {\n // Initialize changeset storage and load from disk\n this.changesetStorage = new StagedChangesetStorage(this.model.rootPath);\n await this.loadChangesetsFromDisk();\n\n this.setupFileWatcher();\n\n // @hono/node-server works identically under Node.js and Bun (both implement\n // Node's http module), so no runtime-specific branching is needed here.\n this._server = serve({\n port,\n fetch: this.app.fetch,\n });\n\n // Node reports bind failures (e.g. EADDRINUSE from a still-running previous\n // instance) asynchronously via an 'error' event rather than a synchronous throw.\n // Waiting on it here turns that into a clean, catchable rejection instead of an\n // unhandled 'error' event that would otherwise crash the whole process via\n // server-entry.ts's uncaughtException handler.\n await new Promise<void>((resolve, reject) => {\n this._server!.once(\"listening\", resolve);\n this._server!.once(\"error\", (err: NodeJS.ErrnoException) => {\n if (err.code === \"EADDRINUSE\") {\n reject(new Error(`Port ${port} is already in use. Try a different port with --port <port>.`));\n } else {\n reject(err);\n }\n });\n });\n\n // Wire up the WebSocket upgrade handler on the underlying HTTP server\n this.injectWebSocket?.(this._server);\n\n console.log(`\u2713 Visualization server running at http://localhost:${port}`);\n }\n\n /**\n * Get the current auth token\n */\n getAuthToken(): string {\n return this.authToken;\n }\n\n /**\n * Check if authentication is enabled\n */\n isAuthEnabled(): boolean {\n return this.authEnabled;\n }\n\n /**\n * Wait for a spawned child process to exit and resolve with its exit code\n * Node's ChildProcess doesn't expose a Bun-style `.exited` promise, so this\n * wraps the \"exit\" event (or the already-set exitCode, if it fired before\n * we started listening).\n */\n private waitForExit(proc: ChildProcess): Promise<number> {\n return new Promise((resolve) => {\n if (proc.exitCode !== null) {\n resolve(proc.exitCode);\n return;\n }\n proc.once(\"exit\", (code) => resolve(code ?? 0));\n });\n }\n\n /**\n * Whether a child process has actually terminated.\n *\n * NOTE: Node's `ChildProcess.killed` means \"kill() was successfully used to send a\n * signal\" \u2014 it does NOT mean the process has exited (a process can ignore SIGTERM\n * and keep running with `.killed === true`). This differs from Bun's\n * `Subprocess.killed`, which does mean \"has the process exited\". Use exitCode/\n * signalCode (set only once the process has actually terminated) instead of\n * `.killed` to decide whether retry/escalation is still needed.\n */\n private hasExited(proc: ChildProcess): boolean {\n return proc.exitCode !== null || proc.signalCode !== null;\n }\n\n /**\n * Kill a process with retry logic and exponential backoff\n * Attempts graceful SIGTERM first, then force SIGKILL if needed\n */\n private async killProcessWithRetry(\n proc: ChildProcess,\n conversationId: string,\n label: string\n ): Promise<void> {\n const maxRetries = 3;\n let attempt = 0;\n const baseDelay = 100; // milliseconds\n\n while (attempt < maxRetries) {\n try {\n if (this.hasExited(proc)) {\n // Process already terminated\n return;\n }\n\n // Try graceful termination with SIGTERM\n proc.kill(\"SIGTERM\");\n\n // Wait for process to exit gracefully\n await new Promise((resolve) => setTimeout(resolve, 500));\n\n if (this.hasExited(proc)) {\n return;\n }\n } catch (error) {\n // Continue to retry\n }\n\n attempt++;\n if (attempt < maxRetries) {\n // Exponential backoff: 100ms, 200ms, 400ms\n const delay = baseDelay * Math.pow(2, attempt - 1);\n await new Promise((resolve) => setTimeout(resolve, delay));\n }\n }\n\n // Force kill if graceful shutdown failed\n try {\n if (!this.hasExited(proc)) {\n proc.kill(\"SIGKILL\");\n console.warn(\n `[${label}] Process for conversation ${conversationId} did not respond to SIGTERM, force killed with SIGKILL`\n );\n }\n } catch (error) {\n throw new Error(\n `Failed to kill process: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n }\n\n /**\n * Stop the server\n */\n stop(): void {\n // Close all WebSocket connections\n for (const client of this.clients) {\n try {\n client.close();\n } catch (error) {\n // Log unexpected errors regardless of VERBOSE flag for operational visibility\n const errorMessage = error instanceof Error ? error.message : String(error);\n if (!errorMessage.includes('already closed')) {\n console.warn(`[SERVER] Unexpected error closing WebSocket client: ${errorMessage}`);\n }\n }\n }\n this.clients.clear();\n\n // Stop file watcher\n if (this.watcher) {\n this.watcher.close?.();\n }\n\n // Stop the server\n if (this._server) {\n this._server.close((error) => {\n if (error && process.env.VERBOSE) {\n console.warn(`[SERVER] Error while closing HTTP server: ${getErrorMessage(error)}`);\n }\n });\n }\n }\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["/**\n * Visualization Server\n * HTTP server with WebSocket support for real-time model updates\n */\n\nimport { OpenAPIHono, createRoute, z } from \"@hono/zod-openapi\";\nimport { cors } from \"hono/cors\";\nimport { swaggerUI } from \"@hono/swagger-ui\";\nimport { serve } from \"@hono/node-server\";\nimport { createNodeWebSocket } from \"@hono/node-ws\";\nimport { spawn, spawnSync, type ChildProcess } from \"child_process\";\nimport chokidar from \"chokidar\";\nimport { randomBytes } from \"crypto\";\nimport { createRequire } from \"module\";\nimport { fileURLToPath } from \"url\";\nimport path from \"path\";\n\nconst require = createRequire(import.meta.url);\nimport { Model } from \"../core/model.js\";\nimport { Element } from \"../core/element.js\";\nimport { Changeset as StagedChangeset } from \"../core/changeset.js\";\nimport { StagedChangesetStorage } from \"../core/staged-changeset-storage.js\";\nimport { CANONICAL_LAYER_NAMES } from \"../core/layers.js\";\nimport { telemetryMiddleware } from \"./telemetry-middleware.js\";\nimport { BaseChatClient } from \"../coding-agents/base-chat-client.js\";\nimport { ClaudeCodeClient } from \"../coding-agents/claude-code-client.js\";\nimport { CopilotClient } from \"../coding-agents/copilot-client.js\";\nimport { detectAvailableClients, selectChatClient } from \"../coding-agents/chat-utils.js\";\nimport { getErrorMessage } from \"../utils/errors.js\";\nimport {\n AnnotationCreateSchema,\n AnnotationUpdateSchema,\n AnnotationReplyCreateSchema,\n LayerNameSchema,\n IdSchema,\n AnnotationFilterSchema,\n ErrorResponseSchema,\n HealthResponseSchema,\n AnnotationSchema,\n AnnotationReplySchema,\n AnnotationsListSchema,\n ChangesetsListSchema,\n ChangesetDetailSchema,\n ModelResponseSchema,\n LayerResponseSchema,\n SpecResponseSchema,\n AnnotationRepliesSchema,\n WSMessageSchema,\n SimpleWSMessageSchema,\n JSONRPCRequestSchema,\n JSONRPCResponseSchema,\n} from \"./schemas.js\";\n\n/**\n * JSON-RPC 2.0 Error Codes\n * Subset of JSON-RPC 2.0 error codes used in this implementation.\n * Standard JSON-RPC 2.0 also defines PARSE_ERROR (-32700) and INVALID_REQUEST (-32600)\n * for protocol-level errors not applicable to WebSocket message handling.\n */\nconst JSONRPC_ERRORS = {\n // Standard JSON-RPC errors\n INVALID_PARAMS: -32602, // Invalid method parameters\n METHOD_NOT_FOUND: -32601, // Method does not exist\n INTERNAL_ERROR: -32603, // Internal server error\n\n // Custom application errors\n NO_CLIENT_AVAILABLE: -32001, // No chat client available\n} as const;\n\n/**\n * Type Casting Note\n *\n * Type casts (`as any`) are used in this file due to @hono/zod-openapi v1.2.1\n * type inference limitations with async middleware handlers. The middleware\n * properly validates and transforms request/response data at runtime via Zod,\n * so the type assertions are safe despite bypassing TypeScript's type checker.\n *\n * TODO: Remove these casts after @hono/zod-openapi improves async handler typing\n * Upstream tracking: https://github.com/honojs/hono/issues/3524\n * Target version: @hono/zod-openapi v2.0.0+ (when async handler typing is improved)\n *\n * Affected endpoints:\n * - Annotation creation (POST /api/annotations)\n * - Annotation retrieval (GET /api/annotations/:id)\n * - Annotation updates (PATCH /api/annotations/:id)\n * - Annotation replacement (PUT /api/annotations/:id)\n * - Annotation deletion (DELETE /api/annotations/:id)\n * - Layer retrieval (GET /api/layers/:layerName)\n * - Schema retrieval (GET /api/spec)\n */\n\n// WebSocket message types derived from Zod schemas for type safety and runtime validation\ntype SimpleWSMessage = z.infer<typeof SimpleWSMessageSchema>;\ntype JSONRPCRequest = z.infer<typeof JSONRPCRequestSchema>;\ntype JSONRPCResponse = z.infer<typeof JSONRPCResponseSchema>;\ntype WSMessage = z.infer<typeof WSMessageSchema>;\n\ntype AnnotationReply = z.infer<typeof AnnotationReplySchema>;\n\n// Derive ClientAnnotation type from AnnotationSchema with proper serialization\ntype ClientAnnotation = z.infer<typeof AnnotationSchema>;\n\n// Type for WebSocket context from Hono's WebSocket helper (hono/ws), shared by all\n// runtime adapters (@hono/node-ws, hono/bun, hono/cloudflare-workers, etc.)\n// Defines the interface for WebSocket operations within the Hono environment\ninterface HonoWSContext {\n send(source: string | ArrayBuffer | Uint8Array | ArrayBufferView, options?: { compress?: boolean }): void;\n close(code?: number, reason?: string): void;\n}\n\nexport interface VisualizationServerOptions {\n authEnabled?: boolean;\n authToken?: string;\n withDanger?: boolean;\n viewerPath?: string;\n}\n\n/**\n * Visualization Server class\n */\nexport class VisualizationServer {\n private app: OpenAPIHono;\n private model: Model;\n private _server?: ReturnType<typeof serve>; // Node HTTP server returned by @hono/node-server\n private upgradeWebSocket?: ReturnType<typeof createNodeWebSocket>[\"upgradeWebSocket\"];\n private injectWebSocket?: ReturnType<typeof createNodeWebSocket>[\"injectWebSocket\"];\n private clients: Set<HonoWSContext> = new Set();\n private watcher?: any;\n private annotations: Map<string, ClientAnnotation> = new Map(); // annotationId -> annotation\n private annotationsByElement: Map<string, Set<string>> = new Map(); // elementId -> Set<annotationId>\n private replies: Map<string, AnnotationReply[]> = new Map(); // annotationId -> replies[]\n private changesets: Map<string, StagedChangeset> = new Map(); // changesetId -> changeset\n private changesetStorage?: StagedChangesetStorage;\n private authToken: string;\n private authEnabled: boolean = true;\n private withDanger: boolean = false; // Danger mode disabled by default\n private viewerPath?: string; // Optional custom viewer path\n private activeChatProcesses: Map<string, ChildProcess> = new Map(); // conversationId -> spawned chat process\n private chatConversationCounter: number = 0;\n private selectedChatClient?: BaseChatClient; // Selected chat client for server\n private chatInitializationError?: Error; // Store initialization error for status endpoint\n private _testClaudeCmdOverride?: string[];\n private _testCopilotCmdOverride?: string[];\n\n /**\n * Valid spec node IDs per layer\n * Dynamically loaded from compiled spec (spec/dist/{layer}.json at module load time)\n * Used to validate and resolve viewer spec node IDs\n * Prevents silent drift when node types are added to spec/schemas/nodes/\n */\n private static readonly VALID_SPEC_NODE_IDS: Record<string, string[]> = VisualizationServer.loadValidSpecNodeIds();\n\n /**\n * Load valid spec node IDs dynamically from bundled spec files\n * Extracts node_types arrays from each layer's compiled schema file\n * This ensures the list is always in sync with the spec build output\n * If any file cannot be loaded, falls back to a minimal safe set\n */\n private static loadValidSpecNodeIds(): Record<string, string[]> {\n const result: Record<string, string[]> = {};\n\n // Get the directory of this file to build absolute path to bundled schemas\n const __dirname = path.dirname(fileURLToPath(import.meta.url));\n const bundledDir = path.join(__dirname, \"..\", \"schemas\", \"bundled\");\n\n for (const layer of CANONICAL_LAYER_NAMES) {\n try {\n // Build absolute path to the layer's compiled spec file\n const specFilePath = path.join(bundledDir, `${layer}.json`);\n\n // Use require() to synchronously load bundled spec file\n // These JSON files are built by \"npm run build\" from spec source\n const spec = require(specFilePath) as {\n layer?: { node_types?: string[] };\n specVersion?: string;\n };\n\n // Extract node_types array from layer object in the compiled spec\n const nodeTypes = spec.layer?.node_types;\n if (Array.isArray(nodeTypes) && nodeTypes.length > 0) {\n result[layer] = nodeTypes;\n } else {\n console.warn(\n `[SPEC-LOAD-001] Layer ${layer} has no node_types in spec file`\n );\n result[layer] = [];\n }\n } catch (error) {\n console.warn(\n `[SPEC-LOAD-002] Failed to load spec node IDs for layer ${layer}: ${getErrorMessage(error)}`\n );\n // Use empty array as fallback - will be caught by usage code\n result[layer] = [];\n }\n }\n\n return result;\n }\n\n /**\n * Fallback spec node IDs used when type extraction from elementId fails\n * These are used when no valid spec node ID can be extracted from the elementId\n * or found through other resolution strategies\n */\n private static readonly LAYER_FALLBACK: Record<string, string> = {\n product: \"business.businessservice\",\n security: \"business.businessprocess\",\n technology: \"application.applicationservice\",\n api: \"business.businessprocess\",\n \"data-model\": \"data-store.database\",\n \"data-store\": \"data-store.database\",\n ux: \"application.applicationcomponent\",\n navigation: \"application.applicationcomponent\",\n apm: \"application.applicationservice\",\n testing: \"business.businessprocess\",\n };\n\n /**\n * All valid spec node IDs across all layers (cached Set for O(1) lookup)\n */\n private static readonly ALL_VALID_IDS: Set<string> = (() => {\n const ids = new Set<string>();\n Object.values(VisualizationServer.VALID_SPEC_NODE_IDS).forEach((layerIds) => {\n layerIds.forEach((id) => ids.add(id));\n });\n return ids;\n })();\n\n constructor(model: Model, options?: VisualizationServerOptions) {\n this.app = new OpenAPIHono();\n this.model = model;\n\n // Set up the WebSocket adapter for this app instance (works under both\n // Node.js and Bun, since @hono/node-server/@hono/node-ws use Node's\n // http module, which Bun also implements).\n this.loadWebSocketAdapter();\n\n // Auth configuration (CLI options override environment variables)\n this.authEnabled = options?.authEnabled ?? process.env.DR_AUTH_ENABLED !== \"false\";\n this.authToken = options?.authToken || process.env.DR_AUTH_TOKEN || this.generateAuthToken();\n this.withDanger = options?.withDanger || false;\n // Use explicit viewerPath if provided, otherwise fall back to the bundled viewer\n this.viewerPath = options?.viewerPath ||\n new URL(\"../viewer\", import.meta.url).pathname;\n\n // Validate cross-field configuration\n if (!this.authEnabled && options?.authToken) {\n console.warn(\n \"[VisualizationServer] Warning: authToken provided but authEnabled is false. \" +\n \"The token will be generated but not used for authentication. \" +\n \"Set authEnabled: true to enable authentication.\"\n );\n }\n\n // Add CORS middleware\n this.app.use(\"/*\", cors());\n\n // Add telemetry middleware to instrument all HTTP requests\n this.app.use(\"/*\", telemetryMiddleware);\n\n // Add authentication middleware (except for health endpoint and root)\n this.app.use(\"/api/*\", async (c, next) => {\n if (!this.authEnabled) {\n return next();\n }\n\n // Check for token in Authorization header or query parameter\n const authHeader = c.req.header(\"Authorization\");\n const queryToken = c.req.query(\"token\");\n\n let providedToken: string | null = null;\n\n if (authHeader && authHeader.startsWith(\"Bearer \")) {\n providedToken = authHeader.substring(7);\n } else if (queryToken) {\n providedToken = queryToken;\n }\n\n if (!providedToken) {\n return c.json({ error: \"Authentication required. Please provide a valid token.\" }, 401);\n }\n\n if (providedToken !== this.authToken) {\n return c.json({ error: \"Invalid authentication token\" }, 403);\n }\n\n return next();\n });\n\n this.setupRoutes();\n\n // Log auth token if enabled\n if (this.authEnabled && process.env.VERBOSE) {\n console.log(`[Auth] Authentication enabled. Token: ${this.authToken}`);\n }\n\n // Initialize chat clients asynchronously\n this.initializeChatClients().catch((error) => {\n this.chatInitializationError = error instanceof Error ? error : new Error(String(error));\n console.error(\"[Chat] Failed to initialize chat clients:\", error);\n });\n }\n\n /**\n * Set up the WebSocket adapter for this server instance\n * This is called in the constructor to support WebSocket functionality via @hono/node-ws,\n * which works identically under Node.js and Bun (both implement Node's http module).\n * Wrapped in try/catch as a defensive guard so a WS setup failure would degrade the\n * server gracefully (no /ws route) rather than crash construction entirely, in case\n * this ever becomes fallible again in the future.\n */\n private loadWebSocketAdapter(): void {\n try {\n const { injectWebSocket, upgradeWebSocket } = createNodeWebSocket({ app: this.app });\n this.injectWebSocket = injectWebSocket;\n this.upgradeWebSocket = upgradeWebSocket;\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n console.warn(\n `[Server] WARNING: Could not set up WebSocket adapter: ${message}. ` +\n `WebSocket endpoints (/ws) will not be available.`\n );\n if (process.env.VERBOSE && error instanceof Error && error.stack) {\n console.debug(\"[Server] Stack trace:\", error.stack);\n }\n }\n }\n\n /**\n * Detect and initialize available chat clients\n */\n private async initializeChatClients(): Promise<void> {\n const clients = await detectAvailableClients();\n\n // Select the chat client from available clients\n // Note: Chat client preference is no longer persisted in manifest\n this.selectedChatClient = selectChatClient(clients, null);\n\n if (this.selectedChatClient && process.env.VERBOSE) {\n console.log(`[Chat] Using chat client: ${this.selectedChatClient.getClientName()}`);\n } else if (clients.length === 0 && process.env.VERBOSE) {\n console.log(\"[Chat] No chat clients available\");\n }\n }\n\n /**\n * Generate a random auth token\n */\n private generateAuthToken(): string {\n return `dr-${randomBytes(24).toString('base64url')}`;\n }\n\n /**\n * Shared annotation update logic for PUT/PATCH handlers\n * Performs partial updates with atomic operations\n */\n private async updateAnnotation(\n annotationId: string,\n body: z.infer<typeof AnnotationUpdateSchema>\n ): Promise<ClientAnnotation | null> {\n const annotation = this.annotations.get(annotationId);\n\n if (!annotation) {\n return null;\n }\n\n // Create new annotation object with updates (atomic update, no direct mutation)\n // This prevents race conditions if the annotation is read during WebSocket broadcast\n const updatedAnnotation: ClientAnnotation = {\n ...annotation,\n content: body.content ?? annotation.content,\n tags: body.tags ?? annotation.tags,\n resolved: body.resolved ?? annotation.resolved,\n updatedAt: new Date().toISOString(),\n };\n\n // Store the updated annotation atomically\n this.annotations.set(annotationId, updatedAnnotation);\n\n // Broadcast to all clients\n await this.broadcastMessage({\n type: \"annotation.updated\",\n annotationId: updatedAnnotation.id,\n timestamp: updatedAnnotation.updatedAt,\n });\n\n return updatedAnnotation;\n }\n\n /**\n * Setup HTTP routes and WebSocket handler\n */\n private setupRoutes(): void {\n // Register bearerAuth security scheme for protected /api/* routes\n this.app.openAPIRegistry.registerComponent('securitySchemes', 'bearerAuth', {\n type: 'http',\n scheme: 'bearer',\n description: 'API token (shown at server startup, pass as Authorization: Bearer <token>)',\n });\n\n // Static viewer HTML at root\n this.app.get(\"/\", (c) => {\n if (this.viewerPath) {\n // Serve custom viewer index.html\n return this.serveCustomViewer(\"index.html\");\n }\n return c.html(this.getViewerHTML());\n });\n\n // Serve static files from custom viewer path if provided\n // Note: This catch-all route must skip API/WS routes by passing to next handler\n if (this.viewerPath) {\n this.app.get(\"/*\", async (c, next) => {\n const requestPath = c.req.path;\n if (process.env.VERBOSE) console.log(`[ROUTE] Catch-all matched: ${requestPath}`);\n\n // Skip API routes and WebSocket - let them be handled by their specific routes\n if (requestPath.startsWith(\"/api/\") || requestPath === \"/ws\" || requestPath === \"/health\" || requestPath === \"/api-spec.yaml\" || requestPath === \"/api-docs\") {\n if (process.env.VERBOSE) console.log(`[ROUTE] Catch-all delegating to next handler for: ${requestPath}`);\n return next(); // Pass to next handler instead of returning 404\n }\n\n if (process.env.VERBOSE) console.log(`[ROUTE] Catch-all serving custom viewer file: ${requestPath}`);\n return this.serveCustomViewer(requestPath.substring(1));\n });\n }\n\n // Health check endpoint\n const healthRoute = createRoute({\n method: 'get',\n path: '/health',\n tags: ['Health'],\n summary: 'Health check',\n description: 'Check if the server is running and healthy',\n responses: {\n 200: {\n description: 'Server is healthy',\n content: {\n 'application/json': {\n schema: HealthResponseSchema,\n },\n },\n },\n },\n });\n\n this.app.openapi(healthRoute, (c) => {\n const response: any = {\n status: this.chatInitializationError ? \"degraded\" : \"ok\",\n version: \"0.1.0\",\n };\n\n // Include warnings if any services failed to initialize\n if (this.chatInitializationError) {\n response.warnings = [\n `Chat service unavailable: ${getErrorMessage(this.chatInitializationError)}`\n ];\n }\n\n return c.json(response);\n });\n\n // Get full model\n const getModelRoute = createRoute({\n method: 'get',\n path: '/api/model',\n tags: ['Model'],\n summary: 'Get full model',\n description: 'Retrieve the complete architecture model across all layers',\n security: [{ bearerAuth: [] }],\n responses: {\n 200: {\n description: 'Model data retrieved successfully',\n content: {\n 'application/json': {\n schema: ModelResponseSchema,\n },\n },\n },\n 500: {\n description: 'Server error',\n content: {\n 'application/json': {\n schema: ErrorResponseSchema,\n },\n },\n },\n },\n });\n\n this.app.openapi(getModelRoute, async (c) => {\n if (process.env.VERBOSE) console.log(`[ROUTE] /api/model handler called`);\n try {\n if (process.env.VERBOSE) console.log(`[ROUTE] /api/model serializing model...`);\n const modelData = await this.serializeModel();\n if (process.env.VERBOSE)\n console.log(\n `[ROUTE] /api/model returning ${Object.keys(modelData.layers || {}).length} layers`\n );\n return c.json(modelData);\n } catch (error) {\n const message = getErrorMessage(error);\n console.error(`[ROUTE] /api/model error: ${message}`);\n return c.json({ error: message }, 500);\n }\n });\n\n // Get specific layer\n const getLayerRoute = createRoute({\n method: 'get',\n path: '/api/layers/:layerName',\n tags: ['Model'],\n summary: 'Get layer',\n description: 'Retrieve a specific layer with all its elements',\n security: [{ bearerAuth: [] }],\n request: {\n params: z.object({ layerName: LayerNameSchema }),\n },\n responses: {\n 200: {\n description: 'Layer retrieved successfully',\n content: {\n 'application/json': {\n schema: LayerResponseSchema,\n },\n },\n },\n 404: {\n description: 'Layer not found',\n content: {\n 'application/json': {\n schema: ErrorResponseSchema,\n },\n },\n },\n 500: {\n description: 'Server error',\n content: {\n 'application/json': {\n schema: ErrorResponseSchema,\n },\n },\n },\n },\n });\n\n // \u2705 Type cast required: See Type Casting Note at top of file\n this.app.openapi(getLayerRoute, (async (c: any) => {\n try {\n const { layerName } = c.req.valid(\"param\");\n const layer = await this.model.getLayer(layerName);\n\n if (!layer) {\n return c.json({ error: \"Layer not found\" }, 404);\n }\n\n const elements = layer.listElements().map((e) => e.toJSON());\n\n return c.json({\n name: layerName,\n elements,\n elementCount: elements.length,\n });\n } catch (error) {\n const message = getErrorMessage(error);\n return c.json({ error: message }, 500);\n }\n }) as any);\n\n // Get JSON Schema specifications\n const getSpecRoute = createRoute({\n method: 'get',\n path: '/api/spec',\n tags: ['Schema'],\n summary: 'Get JSON schemas',\n description: 'Retrieve all JSON Schema definitions used by the system',\n security: [{ bearerAuth: [] }],\n responses: {\n 200: {\n description: 'Schemas retrieved successfully',\n content: {\n 'application/json': {\n schema: SpecResponseSchema,\n },\n },\n },\n 500: {\n description: 'Server error',\n content: {\n 'application/json': {\n schema: ErrorResponseSchema,\n },\n },\n },\n },\n });\n\n // \u2705 Type cast required: See Type Casting Note at top of file\n this.app.openapi(getSpecRoute, (async (c: any) => {\n try {\n const schemas = await this.loadSchemas();\n return c.json({\n version: \"0.1.0\",\n type: \"schema-collection\",\n description: \"JSON Schema definitions from dr CLI\",\n source: \"dr-cli\",\n schemas,\n schemaCount: Object.keys(schemas).length,\n });\n } catch (error) {\n const message = getErrorMessage(error);\n return c.json({ error: message }, 500);\n }\n }) as any);\n\n // Annotations API (spec-compliant routes)\n\n // Get all annotations (optionally filtered by elementId)\n const getAnnotationsRoute = createRoute({\n method: 'get',\n path: '/api/annotations',\n tags: ['Annotations'],\n summary: 'Get annotations',\n description: 'Retrieve all annotations, optionally filtered by element',\n security: [{ bearerAuth: [] }],\n request: {\n query: AnnotationFilterSchema,\n },\n responses: {\n 200: {\n description: 'Annotations retrieved successfully',\n content: {\n 'application/json': {\n schema: AnnotationsListSchema,\n },\n },\n },\n },\n });\n\n this.app.openapi(getAnnotationsRoute, (c) => {\n const query = c.req.valid(\"query\");\n\n const elementId = query.elementId;\n\n if (elementId) {\n // Filter by element\n const annotationIds = this.annotationsByElement.get(elementId) || new Set();\n const annotations = Array.from(annotationIds)\n .map((id) => this.annotations.get(id))\n .filter((a): a is ClientAnnotation => a !== undefined);\n\n return c.json({ annotations });\n } else {\n // Return all annotations\n const annotations = Array.from(this.annotations.values());\n return c.json({ annotations });\n }\n });\n\n // Create annotation\n const createAnnotationRoute = createRoute({\n method: 'post',\n path: '/api/annotations',\n tags: ['Annotations'],\n summary: 'Create annotation',\n description: 'Create a new annotation on a model element',\n security: [{ bearerAuth: [] }],\n request: {\n body: {\n content: {\n 'application/json': {\n schema: AnnotationCreateSchema,\n },\n },\n },\n },\n responses: {\n 201: {\n description: 'Annotation created successfully',\n content: {\n 'application/json': {\n schema: AnnotationSchema,\n },\n },\n },\n 400: {\n description: 'Invalid request body',\n content: {\n 'application/json': {\n schema: ErrorResponseSchema,\n },\n },\n },\n 404: {\n description: 'Element not found',\n content: {\n 'application/json': {\n schema: ErrorResponseSchema,\n },\n },\n },\n 500: {\n description: 'Server error',\n content: {\n 'application/json': {\n schema: ErrorResponseSchema,\n },\n },\n },\n },\n });\n\n this.app.openapi(createAnnotationRoute, async (c) => {\n try {\n const body = c.req.valid(\"json\");\n\n // Verify element exists\n const element = await this.findElement(body.elementId);\n if (!element) {\n return c.json({ error: \"Element not found\" }, 404);\n }\n\n // Create annotation\n const annotation: ClientAnnotation = {\n id: this.generateAnnotationId(),\n elementId: body.elementId,\n author: body.author,\n content: body.content,\n createdAt: new Date().toISOString(),\n tags: body.tags,\n resolved: false, // Default to unresolved\n };\n\n // Store annotation\n this.annotations.set(annotation.id, annotation);\n if (!this.annotationsByElement.has(annotation.elementId)) {\n this.annotationsByElement.set(annotation.elementId, new Set());\n }\n this.annotationsByElement.get(annotation.elementId)!.add(annotation.id);\n\n // Broadcast to all clients\n await this.broadcastMessage({\n type: \"annotation.added\",\n annotationId: annotation.id,\n elementId: annotation.elementId,\n timestamp: annotation.createdAt,\n });\n\n return c.json(annotation, 201);\n } catch (error) {\n const message = getErrorMessage(error);\n return c.json({ error: message }, 500);\n }\n });\n\n // Get individual annotation by ID\n const getAnnotationRoute = createRoute({\n method: 'get',\n path: '/api/annotations/:annotationId',\n tags: ['Annotations'],\n summary: 'Get annotation by ID',\n description: 'Retrieve a specific annotation by its ID',\n security: [{ bearerAuth: [] }],\n request: {\n params: z.object({ annotationId: IdSchema }),\n },\n responses: {\n 200: {\n description: 'Annotation retrieved successfully',\n content: {\n 'application/json': {\n schema: AnnotationSchema,\n },\n },\n },\n 404: {\n description: 'Annotation not found',\n content: {\n 'application/json': {\n schema: ErrorResponseSchema,\n },\n },\n },\n 500: {\n description: 'Server error',\n content: {\n 'application/json': {\n schema: ErrorResponseSchema,\n },\n },\n },\n },\n });\n\n // \u2705 Type cast required: See Type Casting Note at top of file\n this.app.openapi(getAnnotationRoute, (async (c: any) => {\n try {\n const { annotationId } = c.req.valid(\"param\");\n const annotation = this.annotations.get(annotationId);\n\n if (!annotation) {\n return c.json({ error: \"Annotation not found\" }, 404);\n }\n\n return c.json(annotation);\n } catch (error) {\n const message = getErrorMessage(error);\n return c.json({ error: message }, 500);\n }\n }) as any);\n\n // Update annotation (PUT - partial update for backwards compatibility)\n // NOTE: DESIGN RATIONALE - Non-standard PUT behavior:\n // This endpoint performs PARTIAL updates (idempotent) like PATCH, not full replacement\n // like standard HTTP PUT. This is a known deviation from HTTP semantics, chosen for\n // backwards compatibility with existing API consumers. New code should prefer PATCH\n // for partial updates. If full replacement semantics are needed in future, a new\n // endpoint should be created rather than changing this one's behavior.\n const putAnnotationRoute = createRoute({\n method: 'put',\n path: '/api/annotations/:annotationId',\n tags: ['Annotations'],\n summary: 'Update annotation (partial)',\n description: 'Update an existing annotation with partial fields (NOTE: partial updates, use PATCH for recommended behavior)',\n security: [{ bearerAuth: [] }],\n request: {\n params: z.object({ annotationId: IdSchema }),\n body: {\n content: {\n 'application/json': {\n schema: AnnotationUpdateSchema,\n },\n },\n },\n },\n responses: {\n 200: {\n description: 'Annotation updated successfully',\n content: {\n 'application/json': {\n schema: AnnotationSchema,\n },\n },\n },\n 404: {\n description: 'Annotation not found',\n content: {\n 'application/json': {\n schema: ErrorResponseSchema,\n },\n },\n },\n 500: {\n description: 'Server error',\n content: {\n 'application/json': {\n schema: ErrorResponseSchema,\n },\n },\n },\n },\n });\n\n // \u2705 Type cast required: See Type Casting Note at top of file\n this.app.openapi(putAnnotationRoute, (async (c: any) => {\n try {\n const { annotationId } = c.req.valid(\"param\");\n const body = c.req.valid(\"json\");\n\n const updatedAnnotation = await this.updateAnnotation(annotationId, body);\n\n if (!updatedAnnotation) {\n return c.json({ error: \"Annotation not found\" }, 404);\n }\n\n return c.json(updatedAnnotation);\n } catch (error) {\n const message = getErrorMessage(error);\n return c.json({ error: message }, 500);\n }\n }) as any);\n\n // Update annotation (PATCH)\n const patchAnnotationRoute = createRoute({\n method: 'patch',\n path: '/api/annotations/:annotationId',\n tags: ['Annotations'],\n summary: 'Partially update annotation',\n description: 'Partially update an existing annotation (recommended over PUT)',\n security: [{ bearerAuth: [] }],\n request: {\n params: z.object({ annotationId: IdSchema }),\n body: {\n content: {\n 'application/json': {\n schema: AnnotationUpdateSchema,\n },\n },\n },\n },\n responses: {\n 200: {\n description: 'Annotation updated successfully',\n content: {\n 'application/json': {\n schema: AnnotationSchema,\n },\n },\n },\n 404: {\n description: 'Annotation not found',\n content: {\n 'application/json': {\n schema: ErrorResponseSchema,\n },\n },\n },\n 500: {\n description: 'Server error',\n content: {\n 'application/json': {\n schema: ErrorResponseSchema,\n },\n },\n },\n },\n });\n\n // \u2705 Type cast required: See Type Casting Note at top of file\n this.app.openapi(patchAnnotationRoute, (async (c: any) => {\n try {\n const { annotationId } = c.req.valid(\"param\");\n const body = c.req.valid(\"json\");\n\n const updatedAnnotation = await this.updateAnnotation(annotationId, body);\n\n if (!updatedAnnotation) {\n return c.json({ error: \"Annotation not found\" }, 404);\n }\n\n return c.json(updatedAnnotation);\n } catch (error) {\n const message = getErrorMessage(error);\n return c.json({ error: message }, 500);\n }\n }) as any);\n\n // Delete annotation\n const deleteAnnotationRoute = createRoute({\n method: 'delete',\n path: '/api/annotations/:annotationId',\n tags: ['Annotations'],\n summary: 'Delete annotation',\n description: 'Delete an existing annotation',\n security: [{ bearerAuth: [] }],\n request: {\n params: z.object({ annotationId: IdSchema }),\n },\n responses: {\n 204: {\n description: 'Annotation deleted successfully',\n },\n 404: {\n description: 'Annotation not found',\n content: {\n 'application/json': {\n schema: ErrorResponseSchema,\n },\n },\n },\n 500: {\n description: 'Server error',\n content: {\n 'application/json': {\n schema: ErrorResponseSchema,\n },\n },\n },\n },\n });\n\n this.app.openapi(deleteAnnotationRoute, async (c) => {\n try {\n const { annotationId } = c.req.valid(\"param\");\n const annotation = this.annotations.get(annotationId);\n\n if (!annotation) {\n return c.json({ error: \"Annotation not found\" }, 404);\n }\n\n // Remove from storage\n this.annotations.delete(annotationId);\n this.replies.delete(annotationId); // Clean up replies\n const elementAnnotations = this.annotationsByElement.get(annotation.elementId);\n if (elementAnnotations) {\n elementAnnotations.delete(annotationId);\n }\n\n // Broadcast to all clients\n await this.broadcastMessage({\n type: \"annotation.deleted\",\n annotationId,\n timestamp: new Date().toISOString(),\n });\n\n return c.body(null, 204);\n } catch (error) {\n const message = getErrorMessage(error);\n return c.json({ error: message }, 500);\n }\n });\n\n // GET annotation replies\n const getAnnotationRepliesRoute = createRoute({\n method: 'get',\n path: '/api/annotations/:annotationId/replies',\n tags: ['Annotations'],\n summary: 'Get annotation replies',\n description: 'Retrieve all replies to an annotation',\n security: [{ bearerAuth: [] }],\n request: {\n params: z.object({ annotationId: IdSchema }),\n },\n responses: {\n 200: {\n description: 'Replies retrieved successfully',\n content: {\n 'application/json': {\n schema: AnnotationRepliesSchema,\n },\n },\n },\n 404: {\n description: 'Annotation not found',\n content: {\n 'application/json': {\n schema: ErrorResponseSchema,\n },\n },\n },\n },\n });\n\n // \u2705 Type cast required: See Type Casting Note at top of file\n this.app.openapi(getAnnotationRepliesRoute, ((c: any) => {\n const { annotationId } = c.req.valid(\"param\");\n const annotation = this.annotations.get(annotationId);\n\n if (!annotation) {\n return c.json({ error: \"Annotation not found\" }, 404);\n }\n\n const replies = this.replies.get(annotationId) || [];\n return c.json({ replies });\n }) as any);\n\n // POST annotation reply\n const createAnnotationReplyRoute = createRoute({\n method: 'post',\n path: '/api/annotations/:annotationId/replies',\n tags: ['Annotations'],\n summary: 'Create annotation reply',\n description: 'Add a reply to an annotation',\n security: [{ bearerAuth: [] }],\n request: {\n params: z.object({ annotationId: IdSchema }),\n body: {\n content: {\n 'application/json': {\n schema: AnnotationReplyCreateSchema,\n },\n },\n },\n },\n responses: {\n 201: {\n description: 'Reply created successfully',\n content: {\n 'application/json': {\n schema: AnnotationReplySchema,\n },\n },\n },\n 404: {\n description: 'Annotation not found',\n content: {\n 'application/json': {\n schema: ErrorResponseSchema,\n },\n },\n },\n 500: {\n description: 'Server error',\n content: {\n 'application/json': {\n schema: ErrorResponseSchema,\n },\n },\n },\n },\n });\n\n this.app.openapi(createAnnotationReplyRoute, async (c) => {\n try {\n const { annotationId } = c.req.valid(\"param\");\n const annotation = this.annotations.get(annotationId);\n\n if (!annotation) {\n return c.json({ error: \"Annotation not found\" }, 404);\n }\n\n const body = c.req.valid(\"json\");\n\n const reply: AnnotationReply = {\n id: this.generateReplyId(),\n author: body.author,\n content: body.content,\n createdAt: new Date().toISOString(),\n };\n\n // Store reply\n if (!this.replies.has(annotationId)) {\n this.replies.set(annotationId, []);\n }\n this.replies.get(annotationId)!.push(reply);\n\n // Broadcast to all clients\n await this.broadcastMessage({\n type: \"annotation.reply.added\",\n annotationId,\n replyId: reply.id,\n timestamp: reply.createdAt,\n });\n\n return c.json(reply, 201);\n } catch (error) {\n const message = getErrorMessage(error);\n return c.json({ error: message }, 500);\n }\n });\n\n // Changesets API\n\n // Get all changesets\n const getChangesetsRoute = createRoute({\n method: 'get',\n path: '/api/changesets',\n tags: ['Changesets'],\n summary: 'Get changesets',\n description: 'Retrieve a list of all changesets',\n security: [{ bearerAuth: [] }],\n responses: {\n 200: {\n description: 'Changesets retrieved successfully',\n content: {\n 'application/json': {\n schema: ChangesetsListSchema,\n },\n },\n },\n },\n });\n\n this.app.openapi(getChangesetsRoute, (c) => {\n const changesets: Record<string, any> = {};\n\n for (const [id, changeset] of this.changesets) {\n changesets[id] = {\n name: changeset.name,\n status: changeset.status,\n created: changeset.created,\n changes_count: changeset.changes.length,\n };\n }\n\n return c.json({\n version: \"1.0.0\",\n changesets,\n });\n });\n\n // Get specific changeset\n const getChangesetRoute = createRoute({\n method: 'get',\n path: '/api/changesets/:changesetId',\n tags: ['Changesets'],\n summary: 'Get changeset',\n description: 'Retrieve a specific changeset with all its changes',\n security: [{ bearerAuth: [] }],\n request: {\n params: z.object({ changesetId: IdSchema }),\n },\n responses: {\n 200: {\n description: 'Changeset retrieved successfully',\n content: {\n 'application/json': {\n schema: ChangesetDetailSchema,\n },\n },\n },\n 404: {\n description: 'Changeset not found',\n content: {\n 'application/json': {\n schema: ErrorResponseSchema,\n },\n },\n },\n },\n });\n\n // \u2705 Type cast required: See Type Casting Note at top of file\n this.app.openapi(getChangesetRoute, ((c: any) => {\n const { changesetId } = c.req.valid(\"param\");\n const changeset = this.changesets.get(changesetId);\n\n if (!changeset) {\n return c.json({ error: \"Changeset not found\" }, 404);\n }\n\n return c.json({\n id: changeset.id,\n name: changeset.name,\n description: changeset.description,\n status: changeset.status,\n created: changeset.created,\n modified: changeset.modified,\n baseSnapshot: changeset.baseSnapshot,\n stats: changeset.stats,\n changes: changeset.changes,\n });\n }) as any);\n\n // OpenAPI documentation endpoint\n // Note: @hono/zod-openapi always generates 3.1.0, so we specify it here\n // but the version is normalized to 3.0.3 in the generate-openapi.ts script\n this.app.doc('/api-spec.yaml', {\n openapi: '3.1.0',\n info: {\n title: 'Documentation Robotics Visualization Server API',\n version: '0.1.0',\n description: 'API specification for the DR CLI visualization server',\n contact: {\n name: 'Documentation Robotics',\n url: 'https://github.com/tinkermonkey/documentation_robotics',\n },\n license: {\n name: 'ISC',\n },\n },\n servers: [\n { url: 'http://localhost:8080', description: 'Local development server' },\n ],\n tags: [\n { name: 'Health', description: 'Server health and status' },\n { name: 'Schema', description: 'JSON Schema specifications' },\n { name: 'Model', description: 'Architecture model data' },\n { name: 'Changesets', description: 'Model changesets and history' },\n { name: 'Annotations', description: 'User annotations on model elements' },\n { name: 'WebSocket', description: 'Real-time updates via WebSocket' },\n ],\n });\n\n // Swagger UI for interactive API exploration\n this.app.get('/api-docs', swaggerUI({ url: '/api-spec.yaml' }));\n\n // WebSocket endpoint\n // Only register if the WebSocket adapter was set up successfully\n if (this.upgradeWebSocket) {\n this.app.get(\n \"/ws\",\n async (c, next) => {\n // Check WebSocket auth if enabled\n if (this.authEnabled) {\n // Accept token from multiple sources to support diverse client environments:\n // 1. Query parameter: ?token=<token>\n // Why: Works in all browsers. Simple and universally compatible.\n // 2. Sec-WebSocket-Protocol header: token, <actual-token>\n // Why: Browsers cannot set custom HTTP headers during WebSocket handshake\n // for security reasons. The WebSocket spec allows subprotocol negotiation\n // as an alternative auth mechanism. Format: [\"token\", \"YOUR_TOKEN\"]\n // 3. Authorization header: Bearer <token>\n // Why: Standard OAuth2/JWT pattern for non-browser clients (Node.js, cURL, etc)\n // that can set arbitrary headers. Browsers cannot use this for WebSocket due\n // to HTTP header restrictions during handshake.\n\n const queryToken = c.req.query(\"token\");\n\n // Check Sec-WebSocket-Protocol header (browser-compatible method)\n // Format: \"token, <actual-token>\" or \"token,<actual-token>\"\n const wsProtocol = c.req.header(\"Sec-WebSocket-Protocol\");\n let protocolToken: string | null = null;\n if (wsProtocol) {\n const protocols = wsProtocol.split(\",\").map((p) => p.trim());\n // Look for protocol pair: ['token', '<actual-token>']\n const tokenIndex = protocols.indexOf(\"token\");\n if (tokenIndex !== -1 && protocols.length > tokenIndex + 1) {\n protocolToken = protocols[tokenIndex + 1];\n }\n }\n\n // Check Authorization header (non-browser clients)\n const authHeader = c.req.header(\"Authorization\");\n const bearerToken = authHeader?.startsWith(\"Bearer \") ? authHeader.substring(7) : null;\n\n const token = queryToken || protocolToken || bearerToken;\n\n if (!token) {\n return c.json(\n {\n error:\n 'Authentication required. Provide token via: 1) Query param (?token=YOUR_TOKEN), 2) Sec-WebSocket-Protocol header (browser: new WebSocket(url, [\"token\", \"YOUR_TOKEN\"])), or 3) Authorization header (Bearer YOUR_TOKEN)',\n },\n 401\n );\n }\n\n if (token !== this.authToken) {\n return c.json({ error: \"Invalid authentication token\" }, 403);\n }\n\n // If token came from Sec-WebSocket-Protocol, we need to respond with the protocol\n // This is required by the WebSocket spec for subprotocol negotiation\n if (protocolToken) {\n c.header(\"Sec-WebSocket-Protocol\", \"token\");\n }\n }\n\n return next();\n },\n this.upgradeWebSocket(() => ({\n onOpen: async (_evt: any, ws: HonoWSContext) => {\n // Telemetry: Track WebSocket connection\n await this.recordWebSocketEvent(\"ws.connection.open\", {\n \"ws.client_count\": this.clients.size + 1,\n });\n\n this.clients.add(ws);\n\n // Send connected message per spec\n ws.send(\n JSON.stringify({\n type: \"connected\",\n version: \"0.1.0\",\n timestamp: new Date().toISOString(),\n })\n );\n\n if (process.env.VERBOSE) {\n console.log(`[WebSocket] Client connected (total: ${this.clients.size})`);\n }\n },\n\n onClose: async (_evt: any, ws: HonoWSContext) => {\n // Telemetry: Track WebSocket disconnection\n await this.recordWebSocketEvent(\"ws.connection.close\", {\n \"ws.client_count\": this.clients.size - 1,\n });\n\n this.clients.delete(ws);\n if (process.env.VERBOSE) {\n console.log(`[WebSocket] Client disconnected (total: ${this.clients.size})`);\n }\n },\n\n onMessage: async (message: any, ws: HonoWSContext) => {\n const messageStartTime = Date.now();\n let messageType = \"unknown\";\n\n try {\n // Extract the actual message data\n // In Hono's WebSocket helper, message is a MessageEvent with a data property\n const rawData = (message as any).data ?? message;\n\n // Handle different message types (string, Buffer, ArrayBuffer)\n let msgStr: string;\n if (typeof rawData === \"string\") {\n msgStr = rawData;\n } else if (rawData instanceof Buffer || rawData instanceof Uint8Array) {\n msgStr = new TextDecoder().decode(rawData);\n } else {\n msgStr = String(rawData);\n }\n const rawMessage = JSON.parse(msgStr);\n\n // Runtime validation of WebSocket message format\n const validationResult = WSMessageSchema.safeParse(rawMessage);\n if (!validationResult.success) {\n const errorMsg = validationResult.error.issues.map((e) => `${e.path.join('.')}: ${e.message}`).join('; ');\n // Always log validation errors for production debugging\n console.error(\"Invalid WebSocket message format:\", errorMsg);\n ws.send(JSON.stringify({ error: \"Invalid message format\", details: errorMsg }));\n return;\n }\n\n const data = validationResult.data as WSMessage;\n\n // Type-safe discriminated union handling\n if ('jsonrpc' in data && data.jsonrpc === '2.0') {\n // Check if it's a request (has method) or response (has result/error)\n if ('method' in data && typeof data.method === 'string') {\n messageType = (data as JSONRPCRequest).method;\n } else {\n messageType = (data as JSONRPCResponse).id?.toString() || \"jsonrpc-response\";\n }\n } else if ('type' in data) {\n messageType = (data as SimpleWSMessage).type;\n } else {\n messageType = \"unknown\";\n }\n\n // Telemetry: Track message processing\n await this.recordWebSocketEvent(\"ws.message.received\", {\n \"ws.message.type\": messageType,\n \"ws.message.size_bytes\": msgStr.length,\n });\n\n // Check if it's a JSON-RPC message\n if ('jsonrpc' in data && data.jsonrpc === \"2.0\") {\n await this.handleJSONRPCMessage(ws, data);\n } else {\n await this.handleWSMessage(ws, data);\n }\n\n // Telemetry: Track successful message processing\n const durationMs = Date.now() - messageStartTime;\n await this.recordWebSocketEvent(\"ws.message.processed\", {\n \"ws.message.type\": messageType,\n \"ws.message.duration_ms\": durationMs,\n \"ws.message.status\": \"success\",\n });\n } catch (error) {\n const errorMsg = getErrorMessage(error);\n const durationMs = Date.now() - messageStartTime;\n\n // Telemetry: Track message processing error\n await this.recordWebSocketEvent(\"ws.message.error\", {\n \"ws.message.type\": messageType,\n \"ws.message.duration_ms\": durationMs,\n \"ws.message.status\": \"error\",\n \"error.message\": errorMsg,\n });\n\n // Always log message handling errors for operational visibility\n console.warn(\n `[WebSocket] Error handling ${messageType} message (${durationMs}ms): ${errorMsg}`\n );\n ws.send(\n JSON.stringify({\n type: \"error\",\n message: errorMsg,\n })\n );\n }\n },\n\n onError: async (error: any) => {\n const message = getErrorMessage(error);\n\n // Telemetry: Track WebSocket error\n await this.recordWebSocketEvent(\"ws.error\", {\n \"error.type\": error instanceof Error ? error.constructor.name : \"unknown\",\n \"error.message\": message,\n });\n\n // Always log WebSocket errors with full context including stack trace for debugging\n console.error(\"[WebSocket] Error:\", error);\n },\n }))\n );\n }\n }\n\n /**\n * Get OpenAPI specification document\n * Provides a stable public API for OpenAPI spec generation.\n *\n * BEHAVIOR: Returns an OpenAPI spec with the version specified in `config.openapi`.\n * The returned document will have whatever OpenAPI version was passed in the config.\n *\n * NOTE: Hono's getOpenAPI31Document() passes the config directly through and\n * uses the specified version in the output. There is no forced version conversion.\n */\n public getOpenAPIDocument(config: {\n openapi: string;\n info: {\n title: string;\n version: string;\n description?: string;\n contact?: { name?: string; url?: string };\n license?: { name: string };\n };\n servers?: Array<{ url: string; description?: string }>;\n }): any {\n return this.app.getOpenAPI31Document(config);\n }\n\n /**\n * Serialize model to graph format for client consumption.\n *\n * Returns { nodes, links } where:\n * - nodes: flat array of all elements across all layers\n * - links: intra-layer relationships (carry layer_id) and cross-layer\n * references (carry source_layer_id + target_layer_id)\n *\n * Links are deduplicated: if the same relationship appears on multiple\n * elements (e.g. stored on both source and target), only one entry is kept.\n */\n private async serializeModel(): Promise<any> {\n const nodes: any[] = [];\n const linksMap = new Map<string, any>();\n\n // Build element-id \u2192 layer_id lookup so cross-layer reference links can\n // carry accurate source_layer_id / target_layer_id even when the\n // referenced element lives in a different layer.\n const elementLayerMap = new Map<string, string>();\n for (const [layerName, layer] of this.model.layers) {\n for (const e of layer.listElements()) {\n const layerId = e.layer_id || layerName;\n if (e.id) elementLayerMap.set(e.id, layerId);\n // Also index by path so references that use dot-separated slugs resolve correctly.\n // For new-format elements: e.id is a UUID, e.path is the dot-separated slug.\n // For old-format elements: e.id === e.path (both the slug), so no duplicate work.\n if (e.path && e.path !== e.id) elementLayerMap.set(e.path, layerId);\n }\n }\n\n for (const [layerName, layer] of this.model.layers) {\n for (const e of layer.listElements()) {\n const layerId = e.layer_id || layerName;\n\n // Resolve a viewer-compatible spec_node_id so every element renders\n // correctly in the v0.3.0 viewer regardless of which layer it belongs to.\n const viewerSpecNodeId = this.resolveViewerSpecNodeId(e.id, layerId, e.type, e.spec_node_id);\n\n // Node \u2014 all model fields for complete client-side access\n const node: any = {\n id: e.id,\n spec_node_id: viewerSpecNodeId,\n type: e.type,\n layer_id: layerId,\n name: e.name,\n };\n if (e.description) node.description = e.description;\n if (e.attributes && Object.keys(e.attributes).length > 0) node.attributes = e.attributes;\n if (e.source_reference) node.source_reference = e.source_reference;\n if (e.metadata) node.metadata = e.metadata;\n\n nodes.push(node);\n\n // Intra-layer links from relationships\n for (const rel of e.relationships) {\n const linkId = `rel:${rel.source}:${rel.target}:${rel.predicate}`;\n if (!linksMap.has(linkId)) {\n linksMap.set(linkId, {\n id: linkId,\n source: rel.source,\n target: rel.target,\n type: rel.predicate,\n layer_id: layerId,\n });\n }\n }\n\n // Cross-layer links from references\n for (const ref of e.references) {\n const linkId = `ref:${ref.source}:${ref.target}:${ref.type}`;\n if (!linksMap.has(linkId)) {\n linksMap.set(linkId, {\n id: linkId,\n source: ref.source,\n target: ref.target,\n type: ref.type,\n source_layer_id: elementLayerMap.get(ref.source) || layerId,\n target_layer_id: elementLayerMap.get(ref.target) || 'unknown',\n });\n }\n }\n }\n }\n\n // Central relationships.yaml links (added via `dr relationship add`)\n // These are NOT attached to individual elements, so the per-element loop\n // above misses them entirely. Emit them here using the same linksMap so\n // duplicates are naturally de-duplicated.\n for (const rel of this.model.relationships.getAll()) {\n const linkId = `rel:${rel.source}:${rel.target}:${rel.predicate}`;\n if (!linksMap.has(linkId)) {\n const layerId = rel.layer || elementLayerMap.get(rel.source);\n if (!layerId) continue; // Skip \u2014 source element not found and no layer field set\n const link: any = {\n id: linkId,\n source: rel.source,\n target: rel.target,\n type: rel.predicate,\n };\n // Cross-layer relationships set targetLayer; intra-layer relationships do not.\n // Both paths are reachable through normal CLI usage via `dr relationship add`.\n if (rel.targetLayer) {\n link.source_layer_id = layerId;\n link.target_layer_id = rel.targetLayer;\n } else {\n link.layer_id = layerId;\n }\n linksMap.set(linkId, link);\n }\n }\n\n return {\n nodes,\n links: Array.from(linksMap.values()),\n };\n }\n\n /**\n * Resolve viewer spec node ID with fallback logic\n * Handles multiple resolution strategies:\n * 1. Valid pass-through: Return specNodeId if it's already a valid spec node ID\n * 2. Dot-format extraction: Extract type from elementId in \"layer.type.name\" format and construct candidate\n * 3. Hyphen-format extraction: Extract type from elementId in \"layer-type-name\" format and construct candidate\n * 4. Type-parameter fallback: If extracted type differs from type parameter, retry with type parameter\n * 5. Layer-specific fallback: Use LAYER_FALLBACK map for layers with limited viewer support\n *\n * @param elementId - The element ID (can be in dot-format or hyphen-format)\n * @param layer - The layer name (canonical form)\n * @param type - The element type field\n * @param specNodeId - The spec node ID if already known\n * @returns The resolved spec node ID string\n */\n private resolveViewerSpecNodeId(\n elementId: string,\n layer: string,\n type: string,\n specNodeId: string\n ): string {\n // Strategy 1: Valid pass-through - return if specNodeId is valid (anywhere in the model)\n if (specNodeId && VisualizationServer.ALL_VALID_IDS.has(specNodeId)) {\n return specNodeId;\n }\n\n // Strategy 2 & 3: Extract type from elementId and construct candidate\n // Only extract if elementId is not empty\n let extractedType: string | null = null;\n\n if (elementId) {\n // Try dot-format extraction first (layer.type.name)\n if (elementId.includes(\".\")) {\n const parts = elementId.split(\".\");\n if (parts.length >= 2) {\n extractedType = parts[1]; // Second segment is the type\n }\n }\n\n // If no dot-format extraction, try hyphen-format (layer-type-name)\n if (!extractedType && elementId.includes(\"-\")) {\n // For multi-word layers like \"data-model\" or \"data-store\", handle specially\n if (layer === \"data-model\" || layer === \"data-store\") {\n // These layers have hyphens in their names\n // For \"data-model-entity-users\", we need to extract after \"data-model-\"\n const expectedPrefix = layer + \"-\";\n if (elementId.startsWith(expectedPrefix)) {\n const afterPrefix = elementId.substring(expectedPrefix.length);\n const parts = afterPrefix.split(\"-\");\n if (parts.length >= 1) {\n extractedType = parts[0]; // First segment after layer prefix is the type\n }\n }\n } else {\n // For single-word layers, split by hyphen and take second part\n const parts = elementId.split(\"-\");\n if (parts.length >= 2) {\n // Second segment is the type (first is layer)\n extractedType = parts[1];\n }\n }\n }\n }\n\n // If we extracted a type, try to construct a valid candidate\n if (extractedType) {\n const candidate = `${layer}.${extractedType}`;\n const layerIds = VisualizationServer.VALID_SPEC_NODE_IDS[layer] || [];\n if (layerIds.includes(candidate)) {\n return candidate;\n }\n }\n\n // Strategy 4: If type param was provided, try with provided type\n // This applies when:\n // - extractedType is null (e.g., unparseable elementId) but type is valid\n // - extractedType differs from type (prefer extracted over provided)\n if (type && (!extractedType || extractedType !== type)) {\n const candidate = `${layer}.${type}`;\n const layerIds = VisualizationServer.VALID_SPEC_NODE_IDS[layer] || [];\n if (layerIds.includes(candidate)) {\n return candidate;\n }\n }\n\n // Strategy 5: Fall back to layer-specific fallback\n if (layer in VisualizationServer.LAYER_FALLBACK) {\n return VisualizationServer.LAYER_FALLBACK[layer];\n }\n\n // Default fallback for unknown layers\n return \"data-store.database\";\n }\n\n /**\n * Generate unique annotation ID\n * Uses cryptographically secure random bytes for collision-resistant IDs\n */\n private generateAnnotationId(): string {\n return `ann-${Date.now()}-${randomBytes(4).toString('hex')}`;\n }\n\n /**\n * Generate unique reply ID\n * Uses cryptographically secure random bytes for collision-resistant IDs\n */\n private generateReplyId(): string {\n return `reply-${Date.now()}-${randomBytes(4).toString('hex')}`;\n }\n\n /**\n * Broadcast message to all connected WebSocket clients\n */\n private async broadcastMessage(message: any): Promise<void> {\n const messageStr = JSON.stringify(message);\n const messageType = message.type || \"unknown\";\n\n // Telemetry: Track broadcast start\n await this.recordWebSocketEvent(\"ws.broadcast.start\", {\n \"ws.broadcast.type\": messageType,\n \"ws.broadcast.client_count\": this.clients.size,\n \"ws.broadcast.message_size_bytes\": messageStr.length,\n });\n\n let failureCount = 0;\n const failedClients: HonoWSContext[] = [];\n\n for (const client of this.clients) {\n try {\n client.send(messageStr);\n } catch (error) {\n failureCount++;\n failedClients.push(client);\n const msg = getErrorMessage(error);\n // Always log broadcast failures for operational visibility in production\n console.warn(`[WebSocket] Failed to send message to client: ${msg}`);\n }\n }\n\n // Remove dead clients from the active client set\n for (const client of failedClients) {\n this.clients.delete(client);\n }\n\n // Telemetry: Track broadcast completion\n await this.recordWebSocketEvent(\"ws.broadcast.complete\", {\n \"ws.broadcast.type\": messageType,\n \"ws.broadcast.success_count\": this.clients.size - failureCount,\n \"ws.broadcast.failure_count\": failureCount,\n });\n\n // Always warn about significant broadcast failures (>10% failure rate)\n if (failureCount > 0) {\n const failureRate = this.clients.size > 0 ? (failureCount / this.clients.size) * 100 : 0;\n if (failureRate >= 10 || failureCount > 5) {\n console.warn(\n `[WebSocket] Broadcast completed with ${failureCount}/${this.clients.size} client failures (${failureRate.toFixed(1)}% failure rate)`\n );\n }\n }\n }\n\n /**\n * Record WebSocket telemetry event\n * Creates spans for WebSocket operations when telemetry is enabled\n */\n private async recordWebSocketEvent(\n eventName: string,\n attributes: Record<string, any>\n ): Promise<void> {\n // Check if telemetry is enabled (compile-time constant set by esbuild)\n // @ts-ignore - TELEMETRY_ENABLED is a global constant defined at build time\n const isTelemetryEnabled = typeof TELEMETRY_ENABLED !== \"undefined\" ? TELEMETRY_ENABLED : false;\n\n if (!isTelemetryEnabled) {\n return;\n }\n\n try {\n // Dynamic import for tree-shaking\n const { startSpan, endSpan } = await import(\"../telemetry/index.js\");\n\n // Create span for WebSocket event\n const span = startSpan(eventName, attributes);\n\n // End span immediately (WebSocket events are typically instantaneous)\n endSpan(span);\n } catch (error) {\n // Log telemetry failures to enable production debugging\n // Always log errors (not just in DEBUG) to catch silent failures\n console.warn(`[Telemetry] Failed to record WebSocket event: ${getErrorMessage(error)}`);\n }\n }\n\n /**\n * Find an element across all layers\n */\n private async findElement(id: string): Promise<Element | null> {\n for (const layer of this.model.layers.values()) {\n const element = layer.getElement(id);\n if (element) return element;\n }\n return null;\n }\n\n /**\n * Handle WebSocket messages\n */\n private async handleWSMessage(ws: HonoWSContext, data: WSMessage): Promise<void> {\n const simpleMsg = data as SimpleWSMessage;\n switch (simpleMsg.type) {\n case \"subscribe\":\n // Send subscribed confirmation\n const topics = simpleMsg.topics || [\"model\", \"annotations\"];\n ws.send(\n JSON.stringify({\n type: \"subscribed\",\n topics,\n timestamp: new Date().toISOString(),\n })\n );\n\n // Send initial model state\n const modelData = await this.serializeModel();\n ws.send(\n JSON.stringify({\n type: \"model\",\n data: modelData,\n timestamp: new Date().toISOString(),\n })\n );\n break;\n\n case \"ping\":\n // Respond with pong\n ws.send(\n JSON.stringify({\n type: \"pong\",\n timestamp: new Date().toISOString(),\n })\n );\n break;\n\n case \"annotate\":\n if (simpleMsg.annotation) {\n await this.broadcastMessage({\n type: \"annotation\",\n data: simpleMsg.annotation,\n });\n }\n break;\n\n default:\n ws.send(\n JSON.stringify({\n type: \"error\",\n message: `Unknown message type: ${simpleMsg.type}`,\n })\n );\n }\n }\n\n /**\n * Handle JSON-RPC 2.0 messages for chat functionality\n */\n private async handleJSONRPCMessage(ws: HonoWSContext, data: WSMessage): Promise<void> {\n // Only requests have a method; responses have result/error\n if (!('method' in data)) {\n // This is a response, not something we handle in this function\n // Log unexpected protocol patterns for operational visibility\n const responseType = ('result' in data) ? 'success' : ('error' in data) ? 'error' : 'unknown';\n const msgId = ('id' in data) ? data.id : 'unknown';\n console.warn(\n `[WebSocket] Received JSON-RPC ${responseType} response from client (ID: ${msgId}), ignoring`\n );\n return;\n }\n const rpcMsg = data as JSONRPCRequest;\n const { method, params, id } = rpcMsg;\n\n // Helper to send JSON-RPC response\n const sendResponse = (result: any) => {\n ws.send(\n JSON.stringify({\n jsonrpc: \"2.0\",\n result,\n id,\n })\n );\n };\n\n // Helper to send JSON-RPC error\n const sendError = (code: number, message: string, data?: any) => {\n ws.send(\n JSON.stringify({\n jsonrpc: \"2.0\",\n error: { code, message, data },\n id,\n })\n );\n };\n\n try {\n switch (method) {\n case \"chat.status\":\n // Check if any chat client is available\n const hasClient = this.selectedChatClient !== undefined;\n\n // Determine error message: initialization error takes precedence\n let statusError: string | null = null;\n if (this.chatInitializationError) {\n statusError = `Chat initialization failed: ${this.chatInitializationError.message}`;\n } else if (!hasClient) {\n statusError = \"No chat client available. Install Claude Code or GitHub Copilot.\";\n }\n\n sendResponse({\n sdk_available: hasClient,\n sdk_version: hasClient ? this.selectedChatClient!.getClientName() : null,\n error_message: statusError,\n });\n break;\n\n case \"chat.send\":\n // Validate params\n if (!params || typeof params !== 'object' || !('message' in params) || typeof (params as any).message !== 'string' || (params as any).message.trim() === '') {\n sendError(JSONRPC_ERRORS.INVALID_PARAMS, \"Message cannot be empty\");\n return;\n }\n\n // Check if chat client is available\n if (!this.selectedChatClient) {\n sendError(\n JSONRPC_ERRORS.NO_CLIENT_AVAILABLE,\n \"No chat client available. Install Claude Code or GitHub Copilot to enable chat.\"\n );\n return;\n }\n\n // Generate conversation ID\n const conversationId = `conv-${++this.chatConversationCounter}-${Date.now()}`;\n\n // Launch chat with selected client\n await this.launchChat(ws, conversationId, (params as any).message, id);\n break;\n\n case \"chat.cancel\":\n // Find and cancel any active conversation\n let cancelled = false;\n let cancelledConvId = null;\n\n for (const [convId, process] of this.activeChatProcesses) {\n try {\n process.kill();\n this.activeChatProcesses.delete(convId);\n cancelled = true;\n cancelledConvId = convId;\n break;\n } catch (error) {\n console.warn(`[Chat] Failed to cancel process for conversation ${convId}: ${getErrorMessage(error)}`);\n }\n }\n\n sendResponse({\n cancelled,\n conversation_id: cancelledConvId,\n });\n break;\n\n default:\n sendError(JSONRPC_ERRORS.METHOD_NOT_FOUND, `Method not found: ${method}`);\n }\n } catch (error) {\n const errorMsg = getErrorMessage(error);\n sendError(JSONRPC_ERRORS.INTERNAL_ERROR, \"Internal error\", errorMsg);\n }\n }\n\n /**\n * Launch chat with selected client and stream responses via WebSocket\n */\n private async launchChat(\n ws: HonoWSContext,\n conversationId: string,\n message: string,\n requestId: string | number | undefined\n ): Promise<void> {\n if (!this.selectedChatClient) {\n ws.send(\n JSON.stringify({\n jsonrpc: \"2.0\",\n error: {\n code: JSONRPC_ERRORS.NO_CLIENT_AVAILABLE,\n message: \"No chat client available\",\n },\n id: requestId,\n })\n );\n return;\n }\n\n // Route to appropriate client-specific handler\n if (this.selectedChatClient instanceof ClaudeCodeClient) {\n await this.launchClaudeCodeChat(ws, conversationId, message, requestId);\n } else if (this.selectedChatClient instanceof CopilotClient) {\n await this.launchCopilotChat(ws, conversationId, message, requestId);\n } else {\n ws.send(\n JSON.stringify({\n jsonrpc: \"2.0\",\n error: {\n code: JSONRPC_ERRORS.NO_CLIENT_AVAILABLE,\n message: \"Unknown chat client type\",\n },\n id: requestId,\n })\n );\n }\n }\n\n /**\n * Launch Claude Code CLI with dr-architect agent and stream responses\n */\n private async launchClaudeCodeChat(\n ws: HonoWSContext,\n conversationId: string,\n message: string,\n requestId: string | number | undefined\n ): Promise<void> {\n try {\n // Build command arguments\n const cmd = [\"claude\", \"--agent\", \"dr-architect\", \"--print\"];\n\n // Add dangerously-skip-permissions flag if withDanger is enabled\n if (this.withDanger) {\n cmd.push(\"--dangerously-skip-permissions\");\n }\n\n cmd.push(\"--verbose\", \"--output-format\", \"stream-json\");\n\n // Test hook: allow tests to substitute a fixture command for the real claude binary\n if (this._testClaudeCmdOverride) {\n cmd.splice(0, cmd.length, ...this._testClaudeCmdOverride);\n }\n\n // Launch claude with dr-architect agent for comprehensive DR expertise\n const proc = spawn(cmd[0], cmd.slice(1), {\n cwd: this.model.rootPath,\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n });\n\n // Node reports spawn failures (e.g. ENOENT if `claude` isn't on PATH) asynchronously\n // via an 'error' event rather than a synchronous throw. Without this handler, an\n // unhandled 'error' event on the ChildProcess EventEmitter throws and crashes the\n // whole server (server-entry.ts's uncaughtException handler tears the process down)\n // instead of just failing this one chat request.\n proc.on(\"error\", (err) => {\n const errorMsg = getErrorMessage(err);\n console.error(\n `[Claude Code] Failed to spawn subprocess for conversation ${conversationId}: ${errorMsg}`\n );\n this.activeChatProcesses.delete(conversationId);\n try {\n ws.send(\n JSON.stringify({\n jsonrpc: \"2.0\",\n error: {\n code: JSONRPC_ERRORS.INTERNAL_ERROR,\n message: `Failed to launch Claude Code: ${errorMsg}`,\n },\n id: requestId,\n })\n );\n } catch (sendError) {\n console.error(\n `[Claude Code] Failed to send spawn-error notification: ${getErrorMessage(sendError)}`\n );\n }\n });\n // A failed spawn also destroys the stdio streams Node already created, which can\n // raise a second, separate unhandled 'error' event on `proc.stdin` itself. Swallow\n // it here since the handler above already reports the failure to the client.\n proc.stdin?.on(\"error\", () => {});\n\n // Store active process\n this.activeChatProcesses.set(conversationId, proc);\n\n // Send user message via stdin (like Python prototype)\n proc.stdin?.write(message);\n proc.stdin?.end();\n\n // Stream stdout (JSON events)\n const decoder = new TextDecoder();\n\n const streamOutput = async () => {\n let accumulatedText = \"\";\n let buffer = \"\";\n\n try {\n for await (const chunkBuf of proc.stdout!) {\n const chunk = decoder.decode(chunkBuf, { stream: true });\n buffer += chunk;\n\n // Process complete lines (JSON events)\n const lines = buffer.split(\"\\n\");\n buffer = lines.pop() || \"\";\n\n for (const line of lines) {\n if (!line.trim()) continue;\n\n try {\n const event = JSON.parse(line);\n\n if (event.type === \"assistant\") {\n // Extract content blocks from assistant message\n const content = event.message?.content || [];\n for (const block of content) {\n if (block.type === \"text\") {\n accumulatedText += block.text;\n // Send text chunk notification\n ws.send(\n JSON.stringify({\n jsonrpc: \"2.0\",\n method: \"chat.response.chunk\",\n params: {\n conversation_id: conversationId,\n content: block.text,\n is_final: false,\n timestamp: new Date().toISOString(),\n },\n })\n );\n } else if (block.type === \"tool_use\") {\n // Send tool invocation notification\n ws.send(\n JSON.stringify({\n jsonrpc: \"2.0\",\n method: \"chat.tool.invoke\",\n params: {\n conversation_id: conversationId,\n tool_name: block.name,\n tool_input: block.input,\n timestamp: new Date().toISOString(),\n },\n })\n );\n }\n }\n } else if (event.type === \"result\") {\n // Tool result\n ws.send(\n JSON.stringify({\n jsonrpc: \"2.0\",\n method: \"chat.tool.result\",\n params: {\n conversation_id: conversationId,\n result: event.result,\n timestamp: new Date().toISOString(),\n },\n })\n );\n }\n } catch (parseError) {\n // Non-JSON line, send as raw text chunk\n if (process.env.DEBUG) {\n console.debug(`[Chat] Failed to parse JSON chunk: ${getErrorMessage(parseError)}`);\n }\n ws.send(\n JSON.stringify({\n jsonrpc: \"2.0\",\n method: \"chat.response.chunk\",\n params: {\n conversation_id: conversationId,\n content: line + \"\\n\",\n is_final: false,\n timestamp: new Date().toISOString(),\n },\n })\n );\n }\n }\n }\n\n // Process remaining buffer\n if (buffer.trim()) {\n ws.send(\n JSON.stringify({\n jsonrpc: \"2.0\",\n method: \"chat.response.chunk\",\n params: {\n conversation_id: conversationId,\n content: buffer,\n is_final: true,\n timestamp: new Date().toISOString(),\n },\n })\n );\n }\n\n // Wait for process to complete\n const exitCode = await this.waitForExit(proc);\n\n // Clean up\n this.activeChatProcesses.delete(conversationId);\n\n // Send completion response\n ws.send(\n JSON.stringify({\n jsonrpc: \"2.0\",\n result: {\n conversation_id: conversationId,\n status: exitCode === 0 ? \"complete\" : \"error\",\n exit_code: exitCode,\n full_response: accumulatedText,\n timestamp: new Date().toISOString(),\n },\n id: requestId,\n })\n );\n } catch (error) {\n // A process that never actually spawned (see the proc.on(\"error\", ...)\n // handler above, which already reported this failure to the client) has no\n // pid - Node assigns one synchronously only once posix_spawn succeeds. Bail\n // out here instead of sending a second, duplicate JSON-RPC response for the\n // same request or trying to kill a process that never started.\n if (!proc.pid) {\n return;\n }\n\n const errorMsg = getErrorMessage(error);\n\n ws.send(\n JSON.stringify({\n jsonrpc: \"2.0\",\n error: {\n code: JSONRPC_ERRORS.INTERNAL_ERROR,\n message: `Chat failed: ${errorMsg}`,\n },\n id: requestId,\n })\n );\n\n this.activeChatProcesses.delete(conversationId);\n\n try {\n await this.killProcessWithRetry(proc, conversationId, \"Claude Code\");\n } catch (error) {\n console.error(`[Claude Code] Failed to kill process for conversation ${conversationId}: ${getErrorMessage(error)}`);\n }\n }\n };\n\n // Start streaming in background\n streamOutput().catch((err) => {\n const errorMsg = getErrorMessage(err);\n console.error(`[Claude Code] Stream error: ${errorMsg}`);\n\n // Notify client of stream failure via WebSocket\n try {\n ws.send(\n JSON.stringify({\n jsonrpc: \"2.0\",\n error: {\n code: JSONRPC_ERRORS.INTERNAL_ERROR,\n message: `Stream error: ${errorMsg}`,\n },\n id: requestId,\n })\n );\n } catch (sendError) {\n console.error(`[Claude Code] Failed to send stream error notification: ${getErrorMessage(sendError)}`);\n }\n });\n } catch (error) {\n const errorMsg = getErrorMessage(error);\n\n ws.send(\n JSON.stringify({\n jsonrpc: \"2.0\",\n error: {\n code: JSONRPC_ERRORS.INTERNAL_ERROR,\n message: `Failed to launch Claude Code: ${errorMsg}`,\n },\n id: requestId,\n })\n );\n }\n }\n\n /**\n * Launch GitHub Copilot CLI and stream responses via WebSocket\n */\n private async launchCopilotChat(\n ws: HonoWSContext,\n conversationId: string,\n message: string,\n requestId: string | number | undefined\n ): Promise<void> {\n try {\n // Determine which command to use (gh copilot or standalone copilot)\n let cmd: string[];\n\n // Test hook: allow tests to substitute a fixture command for the real copilot binary\n if (this._testCopilotCmdOverride) {\n cmd = [...this._testCopilotCmdOverride];\n } else {\n // Check if gh CLI with copilot extension is available\n const ghResult = spawnSync(\"gh\", [\"copilot\", \"--version\"], {\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n\n // Log why we're falling back to standalone copilot, for debuggability: `gh`\n // missing (ENOENT), `gh` present but the copilot extension isn't installed\n // (non-zero exit), or another spawn failure are otherwise indistinguishable.\n if (ghResult.error) {\n console.warn(\n `[Copilot] 'gh copilot --version' probe failed to spawn: ${getErrorMessage(ghResult.error)}`\n );\n } else if (ghResult.status !== 0 && process.env.VERBOSE) {\n console.log(\n `[Copilot] 'gh copilot' unavailable (exit ${ghResult.status}), falling back to standalone 'copilot'`\n );\n }\n\n if (ghResult.status === 0) {\n cmd = [\"gh\", \"copilot\", \"explain\"];\n\n // Add allow-all-tools flag if withDanger is enabled\n if (this.withDanger) {\n cmd.push(\"--allow-all-tools\");\n }\n\n cmd.push(message);\n } else {\n // Try standalone copilot\n cmd = [\"copilot\", \"explain\"];\n\n // Add allow-all-tools flag if withDanger is enabled\n if (this.withDanger) {\n cmd.push(\"--allow-all-tools\");\n }\n\n cmd.push(message);\n }\n }\n\n // Launch GitHub Copilot\n const proc = spawn(cmd[0], cmd.slice(1), {\n cwd: this.model.rootPath,\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n });\n\n // See the matching handler in launchClaudeCodeChat: Node reports spawn failures\n // (e.g. ENOENT if `gh`/`copilot` isn't on PATH) asynchronously via an 'error'\n // event, and an unhandled one would crash the whole server instead of just this\n // one chat request.\n proc.on(\"error\", (err) => {\n const errorMsg = getErrorMessage(err);\n console.error(\n `[Copilot] Failed to spawn subprocess for conversation ${conversationId}: ${errorMsg}`\n );\n this.activeChatProcesses.delete(conversationId);\n try {\n ws.send(\n JSON.stringify({\n jsonrpc: \"2.0\",\n error: {\n code: JSONRPC_ERRORS.INTERNAL_ERROR,\n message: `Failed to launch GitHub Copilot: ${errorMsg}`,\n },\n id: requestId,\n })\n );\n } catch (sendError) {\n console.error(\n `[Copilot] Failed to send spawn-error notification: ${getErrorMessage(sendError)}`\n );\n }\n });\n // See the matching guard in launchClaudeCodeChat: a failed spawn can also raise a\n // separate unhandled 'error' event on the stdin stream Node already created.\n proc.stdin?.on(\"error\", () => {});\n\n // Store active process\n this.activeChatProcesses.set(conversationId, proc);\n\n // GitHub Copilot outputs plain text/markdown (not JSON)\n const decoder = new TextDecoder();\n\n const streamOutput = async () => {\n let accumulatedText = \"\";\n\n try {\n for await (const chunkBuf of proc.stdout!) {\n const chunk = decoder.decode(chunkBuf, { stream: true });\n accumulatedText += chunk;\n\n // Send text chunk as it arrives\n ws.send(\n JSON.stringify({\n jsonrpc: \"2.0\",\n method: \"chat.response.chunk\",\n params: {\n conversation_id: conversationId,\n content: chunk,\n is_final: false,\n timestamp: new Date().toISOString(),\n },\n })\n );\n }\n\n // Wait for process to complete\n const exitCode = await this.waitForExit(proc);\n\n // Clean up\n this.activeChatProcesses.delete(conversationId);\n\n // Send completion response\n ws.send(\n JSON.stringify({\n jsonrpc: \"2.0\",\n result: {\n conversation_id: conversationId,\n status: exitCode === 0 ? \"complete\" : \"error\",\n exit_code: exitCode,\n full_response: accumulatedText,\n timestamp: new Date().toISOString(),\n },\n id: requestId,\n })\n );\n } catch (error) {\n // See the matching guard in launchClaudeCodeChat: a process that never\n // actually spawned has no pid, and its own proc.on(\"error\", ...) handler\n // above already reported the failure - avoid a duplicate response/kill.\n if (!proc.pid) {\n return;\n }\n\n const errorMsg = getErrorMessage(error);\n\n ws.send(\n JSON.stringify({\n jsonrpc: \"2.0\",\n error: {\n code: JSONRPC_ERRORS.INTERNAL_ERROR,\n message: `Chat failed: ${errorMsg}`,\n },\n id: requestId,\n })\n );\n\n this.activeChatProcesses.delete(conversationId);\n\n try {\n await this.killProcessWithRetry(proc, conversationId, \"Copilot\");\n } catch (error) {\n console.error(`[Copilot] Failed to kill process for conversation ${conversationId}: ${getErrorMessage(error)}`);\n }\n }\n };\n\n // Start streaming in background\n streamOutput().catch((err) => {\n const errorMsg = getErrorMessage(err);\n console.error(`[Copilot] Stream error: ${errorMsg}`);\n\n // Notify client of stream failure via WebSocket\n try {\n ws.send(\n JSON.stringify({\n jsonrpc: \"2.0\",\n error: {\n code: JSONRPC_ERRORS.INTERNAL_ERROR,\n message: `Stream error: ${errorMsg}`,\n },\n id: requestId,\n })\n );\n } catch (sendError) {\n console.error(`[Copilot] Failed to send stream error notification: ${getErrorMessage(sendError)}`);\n }\n });\n } catch (error) {\n const errorMsg = getErrorMessage(error);\n\n ws.send(\n JSON.stringify({\n jsonrpc: \"2.0\",\n error: {\n code: JSONRPC_ERRORS.INTERNAL_ERROR,\n message: `Failed to launch GitHub Copilot: ${errorMsg}`,\n },\n id: requestId,\n })\n );\n }\n }\n\n /**\n * Setup file watcher for model changes\n */\n private setupFileWatcher(): void {\n const modelPath = `${this.model.rootPath}/documentation-robotics/model`;\n\n // Use chokidar for consistent, well-tested cross-platform recursive watching\n // rather than Node's built-in recursive fs.watch (Linux support for the\n // `recursive` option only landed in Node 19.1.0, and its maturity/event\n // semantics still vary more by platform than chokidar's).\n try {\n this.watcher = chokidar.watch(modelPath, {\n persistent: true,\n ignoreInitial: true,\n });\n\n this.watcher.on(\"all\", async (_event: string, path: string) => {\n if (process.env.VERBOSE) {\n console.log(`[Watcher] File changed: ${path}`);\n }\n\n try {\n // Reload model and changesets\n this.model = await Model.load(this.model.rootPath, { lazyLoad: false });\n await this.loadChangesetsFromDisk();\n\n // Broadcast update to all clients\n const modelData = await this.serializeModel();\n const message = JSON.stringify({\n type: \"model.updated\",\n data: modelData,\n timestamp: new Date().toISOString(),\n });\n\n for (const client of this.clients) {\n try {\n client.send(message);\n } catch (error) {\n const msg = getErrorMessage(error);\n console.warn(`[Watcher] Failed to send update: ${msg}`);\n }\n }\n } catch (error) {\n const message = getErrorMessage(error);\n console.error(`[Watcher] Failed to reload model: ${message}`);\n }\n });\n\n this.watcher.on(\"error\", (err: unknown) => {\n console.warn(`[Watcher] Could not watch ${modelPath}: ${getErrorMessage(err)}`);\n });\n } catch (err) {\n console.warn(`[Watcher] Could not watch ${modelPath}: ${err}`);\n }\n }\n\n /**\n * Load all JSON schemas from bundled directory (recursive)\n * Loads schemas from all subdirectories: base/, nodes/, relationships/, etc.\n */\n private async loadSchemas(): Promise<Record<string, any>> {\n const schemasPath = new URL(\"../schemas/bundled/\", import.meta.url).pathname;\n const schemas: Record<string, any> = {};\n\n try {\n const fs = await import(\"fs/promises\");\n const path = await import(\"path\");\n\n // Recursive function to walk through directories\n const walkDirectory = async (dir: string, prefix: string = \"\"): Promise<void> => {\n const entries = await fs.readdir(dir, { withFileTypes: true });\n\n for (const entry of entries) {\n const fullPath = path.join(dir, entry.name);\n const schemaKey = prefix ? `${prefix}/${entry.name}` : entry.name;\n\n if (entry.isDirectory()) {\n // Recursively walk subdirectories\n await walkDirectory(fullPath, schemaKey);\n } else if (entry.isFile() && (entry.name.endsWith(\".schema.json\") || entry.name.endsWith(\".json\"))) {\n // Load JSON schema files\n const content = await fs.readFile(fullPath, \"utf-8\");\n try {\n schemas[schemaKey] = JSON.parse(content);\n } catch (parseError) {\n const parseErrorMsg = parseError instanceof Error ? parseError.message : String(parseError);\n throw new Error(`Invalid JSON in schema file ${fullPath}: ${parseErrorMsg}`);\n }\n }\n }\n };\n\n await walkDirectory(schemasPath);\n\n return schemas;\n } catch (error) {\n const errorMsg = getErrorMessage(error);\n console.error(\"Failed to load schemas:\", error);\n throw new Error(`Failed to load schema files: ${errorMsg}`);\n }\n }\n\n /**\n * Get viewer HTML\n */\n private getViewerHTML(): string {\n return `<!DOCTYPE html>\n<html>\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>Documentation Robotics Viewer</title>\n <style>\n * {\n margin: 0;\n padding: 0;\n box-sizing: border-box;\n }\n\n body {\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;\n background: #f5f5f5;\n color: #333;\n }\n\n header {\n background: white;\n border-bottom: 1px solid #e0e0e0;\n padding: 20px;\n box-shadow: 0 2px 4px rgba(0,0,0,0.1);\n }\n\n .header-content {\n max-width: 1400px;\n margin: 0 auto;\n display: flex;\n justify-content: space-between;\n align-items: center;\n }\n\n h1 {\n font-size: 24px;\n font-weight: 600;\n }\n\n .status-badge {\n display: inline-block;\n padding: 6px 12px;\n border-radius: 4px;\n font-size: 12px;\n font-weight: 500;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n }\n\n .status-badge.connected {\n background: #4caf50;\n color: white;\n }\n\n .status-badge.disconnected {\n background: #f44336;\n color: white;\n }\n\n .container {\n max-width: 1400px;\n margin: 0 auto;\n padding: 20px;\n }\n\n .content {\n display: grid;\n grid-template-columns: 300px 1fr;\n gap: 20px;\n height: calc(100vh - 100px);\n }\n\n .sidebar {\n background: white;\n border-radius: 4px;\n overflow-y: auto;\n box-shadow: 0 2px 4px rgba(0,0,0,0.1);\n padding: 16px;\n }\n\n .main-panel {\n background: white;\n border-radius: 4px;\n overflow-y: auto;\n box-shadow: 0 2px 4px rgba(0,0,0,0.1);\n padding: 20px;\n }\n\n .layer-group {\n margin-bottom: 16px;\n }\n\n .layer-header {\n font-weight: 600;\n padding: 8px 0;\n border-bottom: 2px solid #e0e0e0;\n margin-bottom: 8px;\n cursor: pointer;\n user-select: none;\n display: flex;\n align-items: center;\n }\n\n .layer-header:hover {\n color: #2196F3;\n }\n\n .layer-header::before {\n content: '\u25B6';\n margin-right: 8px;\n display: inline-block;\n transition: transform 0.2s;\n }\n\n .layer-header.expanded::before {\n transform: rotate(90deg);\n }\n\n .layer-elements {\n margin-left: 16px;\n display: none;\n }\n\n .layer-elements.expanded {\n display: block;\n }\n\n .element-item {\n padding: 8px;\n margin-bottom: 4px;\n background: #f9f9f9;\n border-left: 3px solid #e0e0e0;\n cursor: pointer;\n border-radius: 2px;\n transition: all 0.2s;\n font-size: 13px;\n }\n\n .element-item:hover {\n background: #f0f0f0;\n border-left-color: #2196F3;\n }\n\n .element-item.selected {\n background: #e3f2fd;\n border-left-color: #2196F3;\n font-weight: 500;\n }\n\n .element-details {\n min-height: 300px;\n }\n\n .detail-header {\n border-bottom: 2px solid #e0e0e0;\n padding-bottom: 12px;\n margin-bottom: 16px;\n }\n\n .detail-header h2 {\n font-size: 18px;\n margin-bottom: 4px;\n }\n\n .detail-header .element-type {\n font-size: 12px;\n color: #999;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n }\n\n .detail-section {\n margin-bottom: 16px;\n }\n\n .detail-section h3 {\n font-size: 13px;\n font-weight: 600;\n text-transform: uppercase;\n color: #999;\n margin-bottom: 8px;\n letter-spacing: 0.5px;\n }\n\n .detail-value {\n font-size: 14px;\n line-height: 1.6;\n color: #333;\n word-break: break-word;\n }\n\n .annotations-list {\n max-height: 200px;\n overflow-y: auto;\n border: 1px solid #e0e0e0;\n border-radius: 4px;\n padding: 8px;\n }\n\n .annotation-item {\n padding: 8px;\n margin-bottom: 8px;\n background: #fffde7;\n border-left: 3px solid #fbc02d;\n border-radius: 2px;\n font-size: 12px;\n }\n\n .annotation-item .author {\n font-weight: 600;\n color: #666;\n }\n\n .annotation-item .text {\n margin: 4px 0 0 0;\n color: #333;\n }\n\n .annotation-item .timestamp {\n font-size: 11px;\n color: #999;\n margin-top: 4px;\n }\n\n .annotation-form {\n display: grid;\n gap: 8px;\n margin-top: 12px;\n }\n\n .annotation-form input,\n .annotation-form textarea {\n padding: 8px;\n border: 1px solid #e0e0e0;\n border-radius: 4px;\n font-family: inherit;\n font-size: 12px;\n }\n\n .annotation-form textarea {\n resize: vertical;\n min-height: 60px;\n }\n\n .annotation-form button {\n padding: 8px 12px;\n background: #2196F3;\n color: white;\n border: none;\n border-radius: 4px;\n font-weight: 500;\n cursor: pointer;\n transition: background 0.2s;\n }\n\n .annotation-form button:hover {\n background: #1976D2;\n }\n\n .loading {\n text-align: center;\n padding: 40px;\n color: #999;\n }\n\n .loading::after {\n content: '';\n display: inline-block;\n width: 16px;\n height: 16px;\n border: 2px solid #f3f3f3;\n border-top: 2px solid #2196F3;\n border-radius: 50%;\n animation: spin 1s linear infinite;\n margin-left: 8px;\n }\n\n @keyframes spin {\n 0% { transform: rotate(0deg); }\n 100% { transform: rotate(360deg); }\n }\n\n .stats {\n font-size: 12px;\n color: #999;\n margin-bottom: 16px;\n padding-bottom: 12px;\n border-bottom: 1px solid #e0e0e0;\n }\n </style>\n</head>\n<body>\n <header>\n <div class=\"header-content\">\n <div>\n <h1>\uD83D\uDCDA Documentation Robotics Viewer</h1>\n </div>\n <div class=\"status-badge connected\" id=\"status\">\u25CF Connected</div>\n </div>\n </header>\n\n <div class=\"container\">\n <div class=\"content\">\n <div class=\"sidebar\">\n <div class=\"stats\" id=\"stats\">Loading...</div>\n <div id=\"model-tree\">\n <div class=\"loading\">Loading model...</div>\n </div>\n </div>\n\n <div class=\"main-panel\">\n <div id=\"element-details\">\n <div class=\"loading\">Select an element to view details</div>\n </div>\n </div>\n </div>\n </div>\n\n <script>\n let currentModel = null;\n let selectedElementId = null;\n\n const statusEl = document.getElementById('status');\n const modelTreeEl = document.getElementById('model-tree');\n const detailsEl = document.getElementById('element-details');\n const statsEl = document.getElementById('stats');\n\n /**\n * Escape HTML special characters to prevent XSS\n * @param {string} text - Text to escape\n * @returns {string} HTML-escaped text\n */\n function escapeHtml(text) {\n if (!text) return '';\n const div = document.createElement('div');\n div.textContent = text;\n return div.innerHTML;\n }\n\n // Extract token from URL query parameters if present\n const urlParams = new URLSearchParams(window.location.search);\n const token = urlParams.get('token');\n\n // Log token status for debugging\n if (!token) {\n console.warn('[WebSocket] No authentication token found in URL. If authentication is enabled, connection will fail.');\n console.warn('[WebSocket] Expected URL format: http://localhost:PORT?token=YOUR_TOKEN');\n } else {\n console.log('[WebSocket] Found authentication token in URL');\n }\n\n // Build WebSocket URL with token if available\n let wsUrl = \\`ws://\\${window.location.host}/ws\\`;\n if (token) {\n wsUrl += \\`?token=\\${encodeURIComponent(token)}\\`;\n }\n\n console.log('[WebSocket] Connecting to:', wsUrl.replace(/token=[^&]+/, 'token=***'));\n\n const ws = new WebSocket(wsUrl);\n\n ws.addEventListener('open', () => {\n console.log('WebSocket connected');\n updateStatus(true);\n ws.send(JSON.stringify({ type: 'subscribe' }));\n });\n\n ws.addEventListener('message', (event) => {\n try {\n const message = JSON.parse(event.data);\n\n if (message.type === 'model' || message.type === 'model-update') {\n currentModel = message.data;\n renderModel();\n } else if (message.type === 'annotation') {\n // Update annotations for the element\n renderSelectedElement();\n } else if (message.type === 'error') {\n console.error('WebSocket error:', message.message);\n }\n } catch (error) {\n console.error('Failed to parse message:', error);\n }\n });\n\n ws.addEventListener('close', (event) => {\n console.log('WebSocket disconnected', event.code, event.reason);\n updateStatus(false);\n\n // If closed immediately with auth error codes, show helpful message\n if (!token && (event.code === 1002 || event.code === 1008 || event.code === 1006)) {\n showAuthError();\n }\n });\n\n ws.addEventListener('error', (error) => {\n console.error('WebSocket error:', error);\n updateStatus(false);\n\n // If we don't have a token, likely an auth issue\n if (!token) {\n showAuthError();\n }\n });\n\n function showAuthError() {\n const errorDiv = document.createElement('div');\n errorDiv.style.cssText = 'position: fixed; top: 80px; left: 50%; transform: translateX(-50%); background: #f44336; color: white; padding: 16px 24px; border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.3); z-index: 10000; max-width: 500px; text-align: center;';\n errorDiv.innerHTML = \\`\n <strong>\u26A0\uFE0F Authentication Required</strong><br>\n <span style=\"font-size: 14px; margin-top: 8px; display: inline-block;\">\n Please use the full URL with authentication token from the terminal.\n </span>\n \\`;\n document.body.appendChild(errorDiv);\n\n // Auto-dismiss after 10 seconds\n setTimeout(() => {\n errorDiv.style.transition = 'opacity 0.5s';\n errorDiv.style.opacity = '0';\n setTimeout(() => errorDiv.remove(), 500);\n }, 10000);\n }\n\n function updateStatus(connected) {\n if (connected) {\n statusEl.className = 'status-badge connected';\n statusEl.textContent = '\u25CF Connected';\n } else {\n statusEl.className = 'status-badge disconnected';\n statusEl.textContent = '\u25CF Disconnected';\n }\n }\n\n function renderModel() {\n if (!currentModel) return;\n\n // Update stats\n const layerCount = Object.keys(currentModel.layers).length;\n const elementCount = currentModel.totalElements || 0;\n statsEl.textContent = \\`\\${layerCount} layers \u00B7 \\${elementCount} elements\\`;\n\n // Render layers and elements\n modelTreeEl.innerHTML = '';\n const layers = Object.entries(currentModel.layers)\n .sort((a, b) => a[0].localeCompare(b[0]));\n\n for (const [layerName, layerData] of layers) {\n const layerDiv = document.createElement('div');\n layerDiv.className = 'layer-group';\n\n const headerDiv = document.createElement('div');\n headerDiv.className = 'layer-header';\n headerDiv.textContent = layerName;\n headerDiv.addEventListener('click', () => {\n headerDiv.classList.toggle('expanded');\n elementsDiv.classList.toggle('expanded');\n });\n\n const elementsDiv = document.createElement('div');\n elementsDiv.className = 'layer-elements';\n\n for (const element of layerData.elements) {\n const elemDiv = document.createElement('div');\n elemDiv.className = 'element-item';\n const elementSlug = element.path || element.id;\n if (elementSlug === selectedElementId) {\n elemDiv.classList.add('selected');\n }\n elemDiv.innerHTML = \\`\\${escapeHtml(elementSlug)}<br><span style=\"font-size: 11px; color: #999;\">\\${escapeHtml(element.name)}</span>\\`;\n elemDiv.addEventListener('click', () => {\n selectedElementId = elementSlug;\n document.querySelectorAll('.element-item').forEach(el => el.classList.remove('selected'));\n elemDiv.classList.add('selected');\n renderSelectedElement();\n });\n\n elementsDiv.appendChild(elemDiv);\n }\n\n layerDiv.appendChild(headerDiv);\n layerDiv.appendChild(elementsDiv);\n modelTreeEl.appendChild(layerDiv);\n }\n }\n\n function renderSelectedElement() {\n if (!selectedElementId || !currentModel) {\n detailsEl.innerHTML = '<div class=\"loading\">Select an element to view details</div>';\n return;\n }\n\n // Find element in model\n let element = null;\n for (const layer of Object.values(currentModel.layers)) {\n element = layer.elements.find(e => (e.path || e.id) === selectedElementId);\n if (element) break;\n }\n\n if (!element) {\n detailsEl.innerHTML = '<div class=\"loading\">Element not found</div>';\n return;\n }\n\n let html = \\`\n <div class=\"detail-header\">\n <div class=\"element-type\">\\${escapeHtml(element.type)}</div>\n <h2>\\${escapeHtml(element.name)}</h2>\n <div style=\"font-size: 12px; color: #666; margin-top: 4px;\"><code>\\${escapeHtml(element.path || element.id)}</code></div>\n </div>\n \\`;\n\n if (element.description) {\n html += \\`\n <div class=\"detail-section\">\n <h3>Description</h3>\n <div class=\"detail-value\">\\${escapeHtml(element.description)}</div>\n </div>\n \\`;\n }\n\n if (element.properties && Object.keys(element.properties).length > 0) {\n html += \\`\n <div class=\"detail-section\">\n <h3>Properties</h3>\n <div class=\"detail-value\"><pre>\\${escapeHtml(JSON.stringify(element.properties, null, 2))}</pre></div>\n </div>\n \\`;\n }\n\n if (element.annotations && element.annotations.length > 0) {\n html += \\`\n <div class=\"detail-section\">\n <h3>Annotations</h3>\n <div class=\"annotations-list\">\n \\${element.annotations.map(ann => \\`\n <div class=\"annotation-item\">\n <div class=\"author\">\\${escapeHtml(ann.author)}</div>\n <div class=\"text\">\\${escapeHtml(ann.text)}</div>\n <div class=\"timestamp\">\\${new Date(ann.timestamp).toLocaleString()}</div>\n </div>\n \\`).join('')}\n </div>\n </div>\n \\`;\n }\n\n html += \\`\n <div class=\"detail-section\">\n <h3>Add Annotation</h3>\n <form class=\"annotation-form\" onsubmit=\"addAnnotation(event, '\\${escapeHtml(element.path || element.id)}')\">\n <input type=\"text\" placeholder=\"Author name\" id=\"ann-author\" required>\n <textarea placeholder=\"Annotation text\" id=\"ann-text\" required></textarea>\n <button type=\"submit\">Add Annotation</button>\n </form>\n <div id=\"ann-error\" style=\"margin-top: 8px; min-height: 1.2em;\"></div>\n </div>\n \\`;\n\n detailsEl.innerHTML = html;\n }\n\n function addAnnotation(event, elementId) {\n event.preventDefault();\n\n const author = document.getElementById('ann-author').value;\n const text = document.getElementById('ann-text').value;\n\n if (!author || !text) return;\n\n // Send to server using new annotation API\n fetch('/api/annotations', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ elementId, author, content: text })\n })\n .then(r => r.json().then(data => ({ ok: r.ok, data })))\n .then(({ ok, data }) => {\n if (ok && data && data.id) {\n document.getElementById('ann-author').value = '';\n document.getElementById('ann-text').value = '';\n const errorDiv = document.getElementById('ann-error');\n if (errorDiv) errorDiv.textContent = '';\n } else if (!ok) {\n const errorMsg = (data && data.error) ? data.error : 'Failed to create annotation';\n const errorDiv = document.getElementById('ann-error');\n if (errorDiv) {\n errorDiv.textContent = 'Error: ' + errorMsg;\n errorDiv.style.color = '#d32f2f';\n errorDiv.style.marginTop = '8px';\n }\n }\n })\n .catch(err => {\n const errorDiv = document.getElementById('ann-error');\n if (errorDiv) {\n errorDiv.textContent = 'Error: Failed to add annotation: ' + err.message;\n errorDiv.style.color = '#d32f2f';\n errorDiv.style.marginTop = '8px';\n }\n });\n }\n </script>\n</body>\n</html>`;\n }\n\n /**\n * Serve a file from the custom viewer path\n */\n private async serveCustomViewer(filePath: string): Promise<Response> {\n if (process.env.VERBOSE) console.log(`[VIEWER] serveCustomViewer called for: ${filePath}`);\n\n if (!this.viewerPath) {\n console.error(`[VIEWER] Custom viewer path not configured`);\n return new Response(\"Custom viewer path not configured\", { status: 500 });\n }\n\n try {\n const path = await import(\"path\");\n const fs = await import(\"fs/promises\");\n\n // Resolve absolute path and prevent directory traversal\n const fullPath = path.resolve(this.viewerPath, filePath);\n if (process.env.VERBOSE) console.log(`[VIEWER] Resolved path: ${fullPath}`);\n\n // Security check: ensure the resolved path is within viewerPath\n if (!fullPath.startsWith(path.resolve(this.viewerPath))) {\n console.error(`[VIEWER] Security check failed - path outside viewer directory`);\n return new Response(\"Forbidden\", { status: 403 });\n }\n\n // Read file\n if (process.env.VERBOSE) console.log(`[VIEWER] Reading file: ${fullPath}`);\n const content = await fs.readFile(fullPath);\n\n // Determine content type\n const ext = path.extname(fullPath).toLowerCase();\n const contentTypes: Record<string, string> = {\n \".html\": \"text/html\",\n \".css\": \"text/css\",\n \".js\": \"application/javascript\",\n \".json\": \"application/json\",\n \".png\": \"image/png\",\n \".jpg\": \"image/jpeg\",\n \".jpeg\": \"image/jpeg\",\n \".gif\": \"image/gif\",\n \".svg\": \"image/svg+xml\",\n \".ico\": \"image/x-icon\",\n \".woff\": \"font/woff\",\n \".woff2\": \"font/woff2\",\n \".ttf\": \"font/ttf\",\n \".eot\": \"application/vnd.ms-fontobject\",\n };\n\n const contentType = contentTypes[ext] || \"application/octet-stream\";\n\n if (process.env.VERBOSE)\n console.log(`[VIEWER] Successfully read file, returning with Content-Type: ${contentType}`);\n return new Response(content, {\n status: 200,\n headers: {\n \"Content-Type\": contentType,\n },\n });\n } catch (error) {\n if ((error as any).code === \"ENOENT\") {\n console.error(`[VIEWER] File not found: ${filePath}`);\n return new Response(\"File not found\", { status: 404 });\n }\n console.error(`[VIEWER] Error serving custom viewer file ${filePath}:`, error);\n return new Response(\"Internal server error\", { status: 500 });\n }\n }\n\n /**\n * Load all changesets from disk into the in-memory map\n */\n private async loadChangesetsFromDisk(): Promise<void> {\n if (!this.changesetStorage) return;\n try {\n const changesets = await this.changesetStorage.list();\n this.changesets.clear();\n for (const cs of changesets) {\n this.changesets.set(cs.id, cs);\n }\n if (process.env.VERBOSE) {\n console.log(`[Changesets] Loaded ${changesets.length} changeset(s) from disk`);\n }\n } catch (err) {\n console.warn(`[Changesets] Warning: Failed to load changesets from disk: ${err}`);\n }\n }\n\n /**\n * Start the server\n */\n async start(port: number = 8080): Promise<void> {\n // Initialize changeset storage and load from disk\n this.changesetStorage = new StagedChangesetStorage(this.model.rootPath);\n await this.loadChangesetsFromDisk();\n\n this.setupFileWatcher();\n\n // @hono/node-server works identically under Node.js and Bun (both implement\n // Node's http module), so no runtime-specific branching is needed here.\n this._server = serve({\n port,\n fetch: this.app.fetch,\n });\n\n // Node reports bind failures (e.g. EADDRINUSE from a still-running previous\n // instance) asynchronously via an 'error' event rather than a synchronous throw.\n // Waiting on it here turns that into a clean, catchable rejection instead of an\n // unhandled 'error' event that would otherwise crash the whole process via\n // server-entry.ts's uncaughtException handler.\n await new Promise<void>((resolve, reject) => {\n this._server!.once(\"listening\", resolve);\n this._server!.once(\"error\", (err: NodeJS.ErrnoException) => {\n if (err.code === \"EADDRINUSE\") {\n reject(new Error(`Port ${port} is already in use. Try a different port with --port <port>.`));\n } else {\n reject(err);\n }\n });\n });\n\n // Wire up the WebSocket upgrade handler on the underlying HTTP server\n this.injectWebSocket?.(this._server);\n\n console.log(`\u2713 Visualization server running at http://localhost:${port}`);\n }\n\n /**\n * Get the current auth token\n */\n getAuthToken(): string {\n return this.authToken;\n }\n\n /**\n * Check if authentication is enabled\n */\n isAuthEnabled(): boolean {\n return this.authEnabled;\n }\n\n /**\n * Wait for a spawned child process to exit and resolve with its exit code\n * Node's ChildProcess doesn't expose a Bun-style `.exited` promise, so this\n * wraps the \"exit\" event (or the already-set exitCode, if it fired before\n * we started listening).\n */\n private waitForExit(proc: ChildProcess): Promise<number> {\n return new Promise((resolve) => {\n if (proc.exitCode !== null) {\n resolve(proc.exitCode);\n return;\n }\n proc.once(\"exit\", (code) => resolve(code ?? 0));\n });\n }\n\n /**\n * Whether a child process has actually terminated.\n *\n * NOTE: Node's `ChildProcess.killed` means \"kill() was successfully used to send a\n * signal\" \u2014 it does NOT mean the process has exited (a process can ignore SIGTERM\n * and keep running with `.killed === true`). This differs from Bun's\n * `Subprocess.killed`, which does mean \"has the process exited\". Use exitCode/\n * signalCode (set only once the process has actually terminated) instead of\n * `.killed` to decide whether retry/escalation is still needed.\n */\n private hasExited(proc: ChildProcess): boolean {\n return proc.exitCode !== null || proc.signalCode !== null;\n }\n\n /**\n * Kill a process with retry logic and exponential backoff\n * Attempts graceful SIGTERM first, then force SIGKILL if needed\n */\n private async killProcessWithRetry(\n proc: ChildProcess,\n conversationId: string,\n label: string\n ): Promise<void> {\n const maxRetries = 3;\n let attempt = 0;\n const baseDelay = 100; // milliseconds\n\n while (attempt < maxRetries) {\n try {\n if (this.hasExited(proc)) {\n // Process already terminated\n return;\n }\n\n // Try graceful termination with SIGTERM\n proc.kill(\"SIGTERM\");\n\n // Wait for process to exit gracefully\n await new Promise((resolve) => setTimeout(resolve, 500));\n\n if (this.hasExited(proc)) {\n return;\n }\n } catch (error) {\n // Continue to retry\n }\n\n attempt++;\n if (attempt < maxRetries) {\n // Exponential backoff: 100ms, 200ms, 400ms\n const delay = baseDelay * Math.pow(2, attempt - 1);\n await new Promise((resolve) => setTimeout(resolve, delay));\n }\n }\n\n // Force kill if graceful shutdown failed\n try {\n if (!this.hasExited(proc)) {\n proc.kill(\"SIGKILL\");\n console.warn(\n `[${label}] Process for conversation ${conversationId} did not respond to SIGTERM, force killed with SIGKILL`\n );\n }\n } catch (error) {\n throw new Error(\n `Failed to kill process: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n }\n\n /**\n * Stop the server\n */\n stop(): void {\n // Close all WebSocket connections\n for (const client of this.clients) {\n try {\n client.close();\n } catch (error) {\n // Log unexpected errors regardless of VERBOSE flag for operational visibility\n const errorMessage = error instanceof Error ? error.message : String(error);\n if (!errorMessage.includes('already closed')) {\n console.warn(`[SERVER] Unexpected error closing WebSocket client: ${errorMessage}`);\n }\n }\n }\n this.clients.clear();\n\n // Stop file watcher\n if (this.watcher) {\n this.watcher.close?.();\n }\n\n // Stop the server\n if (this._server) {\n this._server.close((error) => {\n if (error && process.env.VERBOSE) {\n console.warn(`[SERVER] Error while closing HTTP server: ${getErrorMessage(error)}`);\n }\n });\n }\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["require", "data", "process", "error", "path"]
|
|
7
7
|
}
|