p2pclaw-api / packages /api /src /config /server.js
Frank-Agnuxo's picture
feat: P2PCLAW API for HF Spaces — ChessBoard Reasoning Engine + full API
e92be04
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);
}