Spaces:
Runtime error
Runtime error
| import express from "express"; | |
| import cors from "cors"; | |
| import net from "net"; | |
| import path from "path"; | |
| import { fileURLToPath } from "url"; | |
| import fs from "fs"; | |
| async function findAvailablePort(startPort) { | |
| return new Promise((resolve) => { | |
| const probe = net.createServer(); | |
| probe.listen(startPort, () => { | |
| probe.close(() => resolve(startPort)); | |
| }); | |
| probe.on("error", () => resolve(findAvailablePort(startPort + 1))); | |
| }); | |
| } | |
| export function setupServer(app) { | |
| const __filename = fileURLToPath(import.meta.url); | |
| const __dirname = path.dirname(__filename); | |
| app.use(cors()); | |
| app.use(express.json()); | |
| // Global Error Handling | |
| process.on('uncaughtException', (err) => { | |
| console.error('CRITICAL: Uncaught Exception:', err); | |
| }); | |
| process.on('unhandledRejection', (reason, promise) => { | |
| console.error('CRITICAL: Unhandled Rejection:', reason); | |
| }); | |
| // Serve static backups | |
| const BACKUP_SERVE_DIR = path.join(__dirname, '../../../../public', 'backups'); // Adjust path as per new monorepo structure | |
| if (!fs.existsSync(BACKUP_SERVE_DIR)) { | |
| fs.mkdirSync(BACKUP_SERVE_DIR, { recursive: true }); | |
| console.log('[Archivist] Created backup directory:', BACKUP_SERVE_DIR); | |
| } | |
| app.use('/backups', express.static(BACKUP_SERVE_DIR)); | |
| // Serve the frontend app | |
| const APP_FRONTEND_DIR = path.join(__dirname, '../../../app'); | |
| // NOTE: Static file serving is intentionally NOT registered here. | |
| // index.js registers app.use(express.static(APP_DIR)) AFTER all API routes, | |
| // which gives routes (including /silicon with content-negotiation) priority | |
| // over static files. Registering static BEFORE routes would intercept /silicon | |
| // and always return HTML regardless of the Accept header. | |
| // Markdown for Agents Middleware | |
| app.use((req, res, next) => { | |
| req.prefersMarkdown = req.headers['accept']?.includes('text/markdown'); | |
| res.setHeader("X-Agent-Friendly", "true"); | |
| res.setHeader("X-Hive-Status", "active"); | |
| res.setHeader("X-Agent-Reward", "available"); | |
| if (req.headers['user-agent']?.toLowerCase().includes('bot') || req.headers['user-agent']?.toLowerCase().includes('agent')) { | |
| res.setHeader("X-Treasure-Path", "/agent-welcome.json"); | |
| } | |
| next(); | |
| }); | |
| // Agent-First header | |
| app.use((req, res, next) => { | |
| res.setHeader('X-Agent-View', 'https://openclaw-agent-01-production-63d8.up.railway.app/agent-view'); | |
| next(); | |
| }); | |
| return app; | |
| } | |
| export async function startServer(app, preferredPort = 3000) { | |
| const port = preferredPort; // Skip probing - trust the environment/config | |
| return new Promise((resolve, reject) => { | |
| const httpServer = app.listen(port, "0.0.0.0", () => { | |
| console.log(`P2PCLAW Gateway running on 0.0.0.0:${port}`); | |
| resolve({ port, httpServer }); | |
| }).on("error", (err) => { | |
| console.error(`[Server] Failed to bind to port ${port}:`, err.message); | |
| reject(err); | |
| }); | |
| }); | |
| } | |
| // Helper to serve markdown | |
| export function serveMarkdown(res, markdown) { | |
| const estimateTokens = (text) => Math.ceil((text || "").length / 4); | |
| const tokens = estimateTokens(markdown); | |
| res.setHeader("Content-Type", "text/markdown; charset=utf-8"); | |
| res.setHeader("x-markdown-tokens", tokens.toString()); | |
| res.setHeader("Vary", "Accept"); | |
| res.send(markdown); | |
| } | |