fix
Browse files- .agent/memory/session.json +2 -2
- app/api/system/vitals/route.ts +23 -19
- components/DashboardVitals.tsx +23 -15
- dist/lib/docker/manager.js +81 -17
- dist/lib/hf/storage.js +27 -18
- dist/lib/idx/idx-engine.js +3 -3
- dist/server.js +3 -3
- lib/docker/manager.ts +99 -18
- lib/hf/storage.ts +38 -25
- lib/idx/idx-engine.ts +3 -3
- lib/system/vitals.ts +17 -0
- server.ts +4 -4
.agent/memory/session.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
{
|
| 2 |
"version": "1.0.0",
|
| 3 |
-
"session_id": "
|
| 4 |
-
"started_at": "2026-04-
|
| 5 |
"workspace": "D:\\Code\\codeverse",
|
| 6 |
"active_task_id": null,
|
| 7 |
"active_agent": null,
|
|
|
|
| 1 |
{
|
| 2 |
"version": "1.0.0",
|
| 3 |
+
"session_id": "e37e0066",
|
| 4 |
+
"started_at": "2026-04-09T20:26:42.840184+05:30",
|
| 5 |
"workspace": "D:\\Code\\codeverse",
|
| 6 |
"active_task_id": null,
|
| 7 |
"active_agent": null,
|
app/api/system/vitals/route.ts
CHANGED
|
@@ -1,37 +1,41 @@
|
|
| 1 |
import { NextResponse } from 'next/server';
|
| 2 |
import os from 'os';
|
|
|
|
| 3 |
|
| 4 |
/**
|
| 5 |
* Infrastructure Health & Vitals API.
|
| 6 |
* Provides real-time metrics for Hugging Face Spaces monitoring.
|
| 7 |
*/
|
|
|
|
|
|
|
| 8 |
export async function GET() {
|
| 9 |
const memory = process.memoryUsage();
|
| 10 |
-
const systemMemory = {
|
| 11 |
free: os.freemem(),
|
| 12 |
total: os.totalmem(),
|
| 13 |
};
|
| 14 |
|
| 15 |
const uptime = process.uptime();
|
| 16 |
-
const load = os.loadavg();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
|
| 18 |
-
|
| 19 |
success: true,
|
| 20 |
-
vitals
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
freeMB: (systemMemory.free / 1024 / 1024).toFixed(0),
|
| 28 |
-
totalMB: (systemMemory.total / 1024 / 1024).toFixed(0),
|
| 29 |
-
loadAverage: load[0].toFixed(2),
|
| 30 |
-
},
|
| 31 |
-
status: {
|
| 32 |
-
uptime: `${Math.floor(uptime / 3600)}h ${Math.floor((uptime % 3600) / 60)}m`,
|
| 33 |
-
nodeVersion: process.version,
|
| 34 |
-
}
|
| 35 |
-
}
|
| 36 |
});
|
| 37 |
}
|
|
|
|
| 1 |
import { NextResponse } from 'next/server';
|
| 2 |
import os from 'os';
|
| 3 |
+
import type { SystemVitals, SystemVitalsResponse } from '@/lib/system/vitals';
|
| 4 |
|
| 5 |
/**
|
| 6 |
* Infrastructure Health & Vitals API.
|
| 7 |
* Provides real-time metrics for Hugging Face Spaces monitoring.
|
| 8 |
*/
|
| 9 |
+
export const dynamic = 'force-dynamic';
|
| 10 |
+
|
| 11 |
export async function GET() {
|
| 12 |
const memory = process.memoryUsage();
|
| 13 |
+
const systemMemory: SystemVitals['systemMemory'] = {
|
| 14 |
free: os.freemem(),
|
| 15 |
total: os.totalmem(),
|
| 16 |
};
|
| 17 |
|
| 18 |
const uptime = process.uptime();
|
| 19 |
+
const load = os.loadavg() as SystemVitals['loadAvg'];
|
| 20 |
+
|
| 21 |
+
const vitals: SystemVitals = {
|
| 22 |
+
rss: memory.rss,
|
| 23 |
+
heapUsed: memory.heapUsed,
|
| 24 |
+
heapTotal: memory.heapTotal,
|
| 25 |
+
loadAvg: load,
|
| 26 |
+
uptime,
|
| 27 |
+
systemMemory,
|
| 28 |
+
nodeVersion: process.version,
|
| 29 |
+
};
|
| 30 |
|
| 31 |
+
const response: SystemVitalsResponse = {
|
| 32 |
success: true,
|
| 33 |
+
vitals,
|
| 34 |
+
};
|
| 35 |
+
|
| 36 |
+
return NextResponse.json(response, {
|
| 37 |
+
headers: {
|
| 38 |
+
'Cache-Control': 'no-store',
|
| 39 |
+
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 40 |
});
|
| 41 |
}
|
components/DashboardVitals.tsx
CHANGED
|
@@ -3,14 +3,9 @@
|
|
| 3 |
import React, { useEffect, useState } from 'react';
|
| 4 |
import { motion, AnimatePresence } from 'framer-motion';
|
| 5 |
import { Activity, Cpu, HardDrive, Zap } from 'lucide-react';
|
|
|
|
| 6 |
|
| 7 |
-
|
| 8 |
-
rss: number;
|
| 9 |
-
heapUsed: number;
|
| 10 |
-
heapTotal: number;
|
| 11 |
-
loadAvg: number[];
|
| 12 |
-
uptime: number;
|
| 13 |
-
}
|
| 14 |
|
| 15 |
/**
|
| 16 |
* Premium Infrastructure Vitals Monitor (2026 Studio Edition)
|
|
@@ -21,21 +16,34 @@ export const DashboardVitals: React.FC = () => {
|
|
| 21 |
const [error, setError] = useState<string | null>(null);
|
| 22 |
|
| 23 |
useEffect(() => {
|
|
|
|
|
|
|
| 24 |
const fetchVitals = async () => {
|
| 25 |
try {
|
| 26 |
-
const res = await fetch('/api/
|
| 27 |
-
if (res.ok) {
|
| 28 |
-
|
| 29 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
}
|
| 31 |
-
} catch {
|
| 32 |
-
setError("Vitals Link Offline");
|
| 33 |
}
|
| 34 |
};
|
| 35 |
|
| 36 |
-
fetchVitals();
|
| 37 |
const interval = setInterval(fetchVitals, 5000);
|
| 38 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 39 |
}, []);
|
| 40 |
|
| 41 |
const formatBytes = (bytes: number) => {
|
|
|
|
| 3 |
import React, { useEffect, useState } from 'react';
|
| 4 |
import { motion, AnimatePresence } from 'framer-motion';
|
| 5 |
import { Activity, Cpu, HardDrive, Zap } from 'lucide-react';
|
| 6 |
+
import type { SystemVitalsResponse } from '@/lib/system/vitals';
|
| 7 |
|
| 8 |
+
type VitalsData = SystemVitalsResponse['vitals'];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
|
| 10 |
/**
|
| 11 |
* Premium Infrastructure Vitals Monitor (2026 Studio Edition)
|
|
|
|
| 16 |
const [error, setError] = useState<string | null>(null);
|
| 17 |
|
| 18 |
useEffect(() => {
|
| 19 |
+
let isDisposed = false;
|
| 20 |
+
|
| 21 |
const fetchVitals = async () => {
|
| 22 |
try {
|
| 23 |
+
const res = await fetch('/api/system/vitals', { cache: 'no-store' });
|
| 24 |
+
if (!res.ok) {
|
| 25 |
+
throw new Error(`Vitals request failed with status ${res.status}`);
|
| 26 |
+
}
|
| 27 |
+
|
| 28 |
+
const data = await res.json() as SystemVitalsResponse;
|
| 29 |
+
if (!isDisposed) {
|
| 30 |
+
setVitals(data.vitals);
|
| 31 |
+
setError(null);
|
| 32 |
+
}
|
| 33 |
+
} catch (error) {
|
| 34 |
+
if (!isDisposed) {
|
| 35 |
+
setError(error instanceof Error ? error.message : 'Vitals Link Offline');
|
| 36 |
}
|
|
|
|
|
|
|
| 37 |
}
|
| 38 |
};
|
| 39 |
|
| 40 |
+
void fetchVitals();
|
| 41 |
const interval = setInterval(fetchVitals, 5000);
|
| 42 |
+
|
| 43 |
+
return () => {
|
| 44 |
+
isDisposed = true;
|
| 45 |
+
clearInterval(interval);
|
| 46 |
+
};
|
| 47 |
}, []);
|
| 48 |
|
| 49 |
const formatBytes = (bytes: number) => {
|
dist/lib/docker/manager.js
CHANGED
|
@@ -147,21 +147,63 @@ function findAvailablePort() {
|
|
| 147 |
}
|
| 148 |
return port;
|
| 149 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 150 |
function resolveCodeServerLaunch() {
|
| 151 |
const overrideBinary = process.env.CODE_SERVER_BIN;
|
| 152 |
if (overrideBinary) {
|
| 153 |
-
|
|
|
|
| 154 |
}
|
| 155 |
if (process.platform === 'win32') {
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
return { command:
|
| 159 |
}
|
| 160 |
-
|
| 161 |
-
|
|
|
|
| 162 |
}
|
|
|
|
| 163 |
}
|
| 164 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 165 |
}
|
| 166 |
/**
|
| 167 |
* Checks if Docker is available in the current environment.
|
|
@@ -236,6 +278,7 @@ async function prewarmWorkspace(config) {
|
|
| 236 |
* INTERNAL: Core provisioning logic with IDX support and auto-provisioning baseline.
|
| 237 |
*/
|
| 238 |
async function performProvisioning(config) {
|
|
|
|
| 239 |
const log = (msg) => {
|
| 240 |
if (config.onLog)
|
| 241 |
config.onLog(`[IDX:ENGINE] ${msg}`);
|
|
@@ -277,6 +320,14 @@ async function performProvisioning(config) {
|
|
| 277 |
const codeServerLaunch = resolveCodeServerLaunch();
|
| 278 |
const shellCommand = codeServerLaunch.command;
|
| 279 |
const args = codeServerLaunch.args;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 280 |
const baseArgs = [
|
| 281 |
'--auth', 'none',
|
| 282 |
'--bind-addr', `127.0.0.1:${port}`,
|
|
@@ -293,11 +344,24 @@ async function performProvisioning(config) {
|
|
| 293 |
};
|
| 294 |
delete spawnEnv.PORT;
|
| 295 |
delete spawnEnv.SERVER_PORT;
|
| 296 |
-
const
|
| 297 |
-
|
| 298 |
-
|
| 299 |
-
|
| 300 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 301 |
log(`Spawning VS Code Orchestrator via ${codeServerLaunch.label} (PID: ${child.pid})...`);
|
| 302 |
let childExited = false;
|
| 303 |
let childFailureReason = null;
|
|
@@ -305,14 +369,14 @@ async function performProvisioning(config) {
|
|
| 305 |
childFailureReason = `IDE_BINARY_FAILURE: ${err.message}`;
|
| 306 |
log(`[FATAL] IDE binary failure: ${err.message}`);
|
| 307 |
});
|
| 308 |
-
child.stdout.on('data', (data) => {
|
| 309 |
const out = data.toString().trim();
|
| 310 |
if (out.includes('listening on'))
|
| 311 |
log(`[IDX:UP] ${out}`);
|
| 312 |
else if (out.length > 0)
|
| 313 |
log(`[IDE:CORE] ${out}`);
|
| 314 |
});
|
| 315 |
-
child.stderr.on('data', (data) => {
|
| 316 |
const err = data.toString().trim();
|
| 317 |
if (err.length > 0)
|
| 318 |
log(`[IDE:ERR] ${err}`);
|
|
@@ -332,7 +396,7 @@ async function performProvisioning(config) {
|
|
| 332 |
while (attempts < 60) {
|
| 333 |
if (childExited) {
|
| 334 |
const rawFailureMessage = childFailureReason !== null && childFailureReason !== void 0 ? childFailureReason : 'IDE_PROCESS_EXITED_BEFORE_HANDSHAKE';
|
| 335 |
-
const failureMessage =
|
| 336 |
? `${rawFailureMessage}. code-server could not be bootstrapped via npx. Install code-server globally or set CODE_SERVER_BIN.`
|
| 337 |
: rawFailureMessage;
|
| 338 |
log(`[FATAL] IDE bootstrap aborted before handshake: ${failureMessage}`);
|
|
@@ -358,7 +422,7 @@ async function performProvisioning(config) {
|
|
| 358 |
return finalResult;
|
| 359 |
}
|
| 360 |
}
|
| 361 |
-
catch (
|
| 362 |
if (attempts % 5 === 0)
|
| 363 |
log(`[INFO] Scanning for IDE heartbeat... (Attempt ${attempts}/60)`);
|
| 364 |
if (attempts === 15)
|
|
@@ -383,7 +447,7 @@ async function performProvisioning(config) {
|
|
| 383 |
const error = e instanceof Error ? e.message : String(e);
|
| 384 |
log(`[FATAL] Provisioning pipeline collapsed: ${error}`);
|
| 385 |
exports.nativeProcesses.delete(config.id);
|
| 386 |
-
const errResult = { success: false, error:
|
| 387 |
exports.provisioningBus.emit(`error:${config.id}`, errResult);
|
| 388 |
return errResult;
|
| 389 |
}
|
|
|
|
| 147 |
}
|
| 148 |
return port;
|
| 149 |
}
|
| 150 |
+
function resolveExecutableOnPath(candidates) {
|
| 151 |
+
const locatorCommand = process.platform === 'win32' ? 'where' : 'which';
|
| 152 |
+
for (const candidate of candidates) {
|
| 153 |
+
try {
|
| 154 |
+
const output = (0, child_process_1.execFileSync)(locatorCommand, [candidate], {
|
| 155 |
+
encoding: 'utf8',
|
| 156 |
+
stdio: ['ignore', 'pipe', 'ignore'],
|
| 157 |
+
});
|
| 158 |
+
const resolvedPath = output
|
| 159 |
+
.split(/\r?\n/)
|
| 160 |
+
.map((line) => line.trim())
|
| 161 |
+
.find(Boolean);
|
| 162 |
+
if (resolvedPath) {
|
| 163 |
+
return resolvedPath;
|
| 164 |
+
}
|
| 165 |
+
}
|
| 166 |
+
catch (_a) {
|
| 167 |
+
// Fall through to the next candidate.
|
| 168 |
+
}
|
| 169 |
+
}
|
| 170 |
+
return null;
|
| 171 |
+
}
|
| 172 |
+
function quoteWindowsCmdArg(value) {
|
| 173 |
+
if (value.length === 0) {
|
| 174 |
+
return '""';
|
| 175 |
+
}
|
| 176 |
+
if (!/[ \t"]/.test(value)) {
|
| 177 |
+
return value;
|
| 178 |
+
}
|
| 179 |
+
return `"${value.replace(/"/g, '""')}"`;
|
| 180 |
+
}
|
| 181 |
function resolveCodeServerLaunch() {
|
| 182 |
const overrideBinary = process.env.CODE_SERVER_BIN;
|
| 183 |
if (overrideBinary) {
|
| 184 |
+
const useShell = process.platform === 'win32' && /\.(cmd|bat)$/i.test(overrideBinary);
|
| 185 |
+
return { command: overrideBinary, args: [], label: overrideBinary, usesNpx: false, useShell };
|
| 186 |
}
|
| 187 |
if (process.platform === 'win32') {
|
| 188 |
+
const codeServerBinary = resolveExecutableOnPath(['code-server.exe', 'code-server']);
|
| 189 |
+
if (codeServerBinary) {
|
| 190 |
+
return { command: codeServerBinary, args: [], label: 'code-server', usesNpx: false, useShell: false };
|
| 191 |
}
|
| 192 |
+
const npxCliPath = path_1.default.join(path_1.default.dirname(process.execPath), 'node_modules', 'npm', 'bin', 'npx-cli.js');
|
| 193 |
+
if (fs_1.default.existsSync(npxCliPath)) {
|
| 194 |
+
return { command: process.execPath, args: [npxCliPath, '--yes', 'code-server'], label: 'node npx-cli.js code-server', usesNpx: true, useShell: false };
|
| 195 |
}
|
| 196 |
+
throw new Error('CODE_SERVER_BIN_NOT_FOUND');
|
| 197 |
}
|
| 198 |
+
const codeServerBinary = resolveExecutableOnPath(['code-server']);
|
| 199 |
+
if (codeServerBinary) {
|
| 200 |
+
return { command: codeServerBinary, args: [], label: 'code-server', usesNpx: false, useShell: false };
|
| 201 |
+
}
|
| 202 |
+
const npxBinary = resolveExecutableOnPath(['npx']);
|
| 203 |
+
if (npxBinary) {
|
| 204 |
+
return { command: npxBinary, args: ['--yes', 'code-server'], label: 'npx code-server', usesNpx: true, useShell: false };
|
| 205 |
+
}
|
| 206 |
+
throw new Error('CODE_SERVER_BIN_NOT_FOUND');
|
| 207 |
}
|
| 208 |
/**
|
| 209 |
* Checks if Docker is available in the current environment.
|
|
|
|
| 278 |
* INTERNAL: Core provisioning logic with IDX support and auto-provisioning baseline.
|
| 279 |
*/
|
| 280 |
async function performProvisioning(config) {
|
| 281 |
+
var _a, _b;
|
| 282 |
const log = (msg) => {
|
| 283 |
if (config.onLog)
|
| 284 |
config.onLog(`[IDX:ENGINE] ${msg}`);
|
|
|
|
| 320 |
const codeServerLaunch = resolveCodeServerLaunch();
|
| 321 |
const shellCommand = codeServerLaunch.command;
|
| 322 |
const args = codeServerLaunch.args;
|
| 323 |
+
const spawnCwd = (() => {
|
| 324 |
+
try {
|
| 325 |
+
return fs_1.default.realpathSync(workspacePath);
|
| 326 |
+
}
|
| 327 |
+
catch (_a) {
|
| 328 |
+
return workspacePath;
|
| 329 |
+
}
|
| 330 |
+
})();
|
| 331 |
const baseArgs = [
|
| 332 |
'--auth', 'none',
|
| 333 |
'--bind-addr', `127.0.0.1:${port}`,
|
|
|
|
| 344 |
};
|
| 345 |
delete spawnEnv.PORT;
|
| 346 |
delete spawnEnv.SERVER_PORT;
|
| 347 |
+
const launchArgs = [...args, ...baseArgs];
|
| 348 |
+
const launchCommand = codeServerLaunch.useShell ? (process.env.ComSpec || 'cmd.exe') : shellCommand;
|
| 349 |
+
const launchCommandArgs = codeServerLaunch.useShell
|
| 350 |
+
? ['/d', '/s', '/c', [quoteWindowsCmdArg(shellCommand), ...launchArgs.map(quoteWindowsCmdArg)].join(' ')]
|
| 351 |
+
: launchArgs;
|
| 352 |
+
log(`IDE launch prepared. Binary: ${shellCommand} | cwd: ${spawnCwd} | target: ${workspacePath}`);
|
| 353 |
+
let child;
|
| 354 |
+
try {
|
| 355 |
+
child = (0, child_process_1.spawn)(launchCommand, launchCommandArgs, {
|
| 356 |
+
env: spawnEnv,
|
| 357 |
+
cwd: spawnCwd,
|
| 358 |
+
shell: false
|
| 359 |
+
});
|
| 360 |
+
}
|
| 361 |
+
catch (error) {
|
| 362 |
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
| 363 |
+
throw new Error(`IDE_SPAWN_CONFIGURATION_REJECTED: ${errorMessage}`);
|
| 364 |
+
}
|
| 365 |
log(`Spawning VS Code Orchestrator via ${codeServerLaunch.label} (PID: ${child.pid})...`);
|
| 366 |
let childExited = false;
|
| 367 |
let childFailureReason = null;
|
|
|
|
| 369 |
childFailureReason = `IDE_BINARY_FAILURE: ${err.message}`;
|
| 370 |
log(`[FATAL] IDE binary failure: ${err.message}`);
|
| 371 |
});
|
| 372 |
+
(_a = child.stdout) === null || _a === void 0 ? void 0 : _a.on('data', (data) => {
|
| 373 |
const out = data.toString().trim();
|
| 374 |
if (out.includes('listening on'))
|
| 375 |
log(`[IDX:UP] ${out}`);
|
| 376 |
else if (out.length > 0)
|
| 377 |
log(`[IDE:CORE] ${out}`);
|
| 378 |
});
|
| 379 |
+
(_b = child.stderr) === null || _b === void 0 ? void 0 : _b.on('data', (data) => {
|
| 380 |
const err = data.toString().trim();
|
| 381 |
if (err.length > 0)
|
| 382 |
log(`[IDE:ERR] ${err}`);
|
|
|
|
| 396 |
while (attempts < 60) {
|
| 397 |
if (childExited) {
|
| 398 |
const rawFailureMessage = childFailureReason !== null && childFailureReason !== void 0 ? childFailureReason : 'IDE_PROCESS_EXITED_BEFORE_HANDSHAKE';
|
| 399 |
+
const failureMessage = codeServerLaunch.usesNpx
|
| 400 |
? `${rawFailureMessage}. code-server could not be bootstrapped via npx. Install code-server globally or set CODE_SERVER_BIN.`
|
| 401 |
: rawFailureMessage;
|
| 402 |
log(`[FATAL] IDE bootstrap aborted before handshake: ${failureMessage}`);
|
|
|
|
| 422 |
return finalResult;
|
| 423 |
}
|
| 424 |
}
|
| 425 |
+
catch (_c) {
|
| 426 |
if (attempts % 5 === 0)
|
| 427 |
log(`[INFO] Scanning for IDE heartbeat... (Attempt ${attempts}/60)`);
|
| 428 |
if (attempts === 15)
|
|
|
|
| 447 |
const error = e instanceof Error ? e.message : String(e);
|
| 448 |
log(`[FATAL] Provisioning pipeline collapsed: ${error}`);
|
| 449 |
exports.nativeProcesses.delete(config.id);
|
| 450 |
+
const errResult = { success: false, error: `PROVISIONING_FAILED: ${error}` };
|
| 451 |
exports.provisioningBus.emit(`error:${config.id}`, errResult);
|
| 452 |
return errResult;
|
| 453 |
}
|
dist/lib/hf/storage.js
CHANGED
|
@@ -15,6 +15,18 @@ class HFStorage {
|
|
| 15 |
static get isPersistenceRuntimeEnabled() {
|
| 16 |
return Boolean(env_config_1.ENV_CONFIG.SPACE_ID && this.HF_TOKEN && this.HF_DATASET_ID && process.platform !== 'win32');
|
| 17 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 18 |
/**
|
| 19 |
* Internal helper for asynchronous execution with logging.
|
| 20 |
*/
|
|
@@ -25,7 +37,7 @@ class HFStorage {
|
|
| 25 |
HF_TOKEN: this.HF_TOKEN,
|
| 26 |
HF_HOME: '/tmp/.cache/huggingface',
|
| 27 |
TMPDIR: '/tmp',
|
| 28 |
-
PATH: `/home/node/.local/bin:/home/node/.nix-profile/bin:/usr/local/bin:/usr/bin:${process.env.PATH}`
|
| 29 |
};
|
| 30 |
const child = (0, child_process_1.spawn)('/bin/bash', ['-c', command], {
|
| 31 |
env: spawnEnv
|
|
@@ -60,16 +72,15 @@ class HFStorage {
|
|
| 60 |
const tmpDir = '/tmp/hf-sync';
|
| 61 |
if (!fs_1.default.existsSync(tmpDir))
|
| 62 |
fs_1.default.mkdirSync(tmpDir, { recursive: true });
|
| 63 |
-
// 🟢 DELTA SYNCING: Only sync the specific workspace and IDE state directories
|
| 64 |
const home = process.env.HOME || '/home/node';
|
| 65 |
-
const
|
| 66 |
-
for (const
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
const cmd = `(command -v huggingface-cli >/dev/null && huggingface-cli download ${this.HF_DATASET_ID} --local-dir ${
|
| 71 |
-
onLog === null || onLog === void 0 ? void 0 : onLog(`[HF:STORAGE] Restoring ${
|
| 72 |
-
await this.execAsync(cmd, onLog).catch(() => { });
|
| 73 |
}
|
| 74 |
onLog === null || onLog === void 0 ? void 0 : onLog(`[HF:STORAGE] Profile restoration complete.`);
|
| 75 |
}
|
|
@@ -86,15 +97,13 @@ class HFStorage {
|
|
| 86 |
return;
|
| 87 |
try {
|
| 88 |
onLog === null || onLog === void 0 ? void 0 : onLog(`[HF:STORAGE] Saving persistent profile to '${this.HF_DATASET_ID}'...`);
|
| 89 |
-
const
|
| 90 |
-
const
|
| 91 |
-
|
| 92 |
-
const localPath = path_1.default.join(home, dir);
|
| 93 |
-
if (!fs_1.default.existsSync(localPath))
|
| 94 |
continue;
|
| 95 |
-
const cmd = `(command -v huggingface-cli >/dev/null && huggingface-cli upload ${this.HF_DATASET_ID} ${localPath} ${
|
| 96 |
-
onLog === null || onLog === void 0 ? void 0 : onLog(`[HF:STORAGE] Performing differential backup of ${
|
| 97 |
-
await this.execAsync(cmd, onLog).catch(err => onLog === null || onLog === void 0 ? void 0 : onLog(`[WARN] Sync failed for ${
|
| 98 |
}
|
| 99 |
onLog === null || onLog === void 0 ? void 0 : onLog(`[HF:STORAGE] Profile backup successful.`);
|
| 100 |
}
|
|
|
|
| 15 |
static get isPersistenceRuntimeEnabled() {
|
| 16 |
return Boolean(env_config_1.ENV_CONFIG.SPACE_ID && this.HF_TOKEN && this.HF_DATASET_ID && process.platform !== 'win32');
|
| 17 |
}
|
| 18 |
+
static getPersistenceEntries() {
|
| 19 |
+
const home = process.env.HOME || '/home/node';
|
| 20 |
+
const relativeWorkspaceRoot = path_1.default.relative(home, this.WORKSPACE_ROOT).replace(/\\/g, '/');
|
| 21 |
+
const workspaceDatasetPath = relativeWorkspaceRoot.startsWith('..')
|
| 22 |
+
? path_1.default.basename(this.WORKSPACE_ROOT)
|
| 23 |
+
: relativeWorkspaceRoot;
|
| 24 |
+
return [
|
| 25 |
+
{ datasetPath: workspaceDatasetPath, localPath: this.WORKSPACE_ROOT },
|
| 26 |
+
{ datasetPath: '.vscode-server', localPath: path_1.default.join(home, '.vscode-server') },
|
| 27 |
+
{ datasetPath: '.config/code-server', localPath: path_1.default.join(home, '.config', 'code-server') },
|
| 28 |
+
];
|
| 29 |
+
}
|
| 30 |
/**
|
| 31 |
* Internal helper for asynchronous execution with logging.
|
| 32 |
*/
|
|
|
|
| 37 |
HF_TOKEN: this.HF_TOKEN,
|
| 38 |
HF_HOME: '/tmp/.cache/huggingface',
|
| 39 |
TMPDIR: '/tmp',
|
| 40 |
+
PATH: `/home/node/.local/bin:/home/node/.nix-profile/bin:/usr/local/bin:/usr/bin:${process.env.PATH}`,
|
| 41 |
};
|
| 42 |
const child = (0, child_process_1.spawn)('/bin/bash', ['-c', command], {
|
| 43 |
env: spawnEnv
|
|
|
|
| 72 |
const tmpDir = '/tmp/hf-sync';
|
| 73 |
if (!fs_1.default.existsSync(tmpDir))
|
| 74 |
fs_1.default.mkdirSync(tmpDir, { recursive: true });
|
|
|
|
| 75 |
const home = process.env.HOME || '/home/node';
|
| 76 |
+
const persistenceEntries = this.getPersistenceEntries();
|
| 77 |
+
for (const entry of persistenceEntries) {
|
| 78 |
+
if (!fs_1.default.existsSync(entry.localPath)) {
|
| 79 |
+
fs_1.default.mkdirSync(entry.localPath, { recursive: true });
|
| 80 |
+
}
|
| 81 |
+
const cmd = `(command -v huggingface-cli >/dev/null && huggingface-cli download ${this.HF_DATASET_ID} --local-dir ${home} --include "${entry.datasetPath}/*" --token ${this.HF_TOKEN}) || (hf download ${this.HF_DATASET_ID} --local-dir ${home} --include "${entry.datasetPath}/*" --token ${this.HF_TOKEN})`;
|
| 82 |
+
onLog === null || onLog === void 0 ? void 0 : onLog(`[HF:STORAGE] Restoring ${entry.datasetPath} from differential profile...`);
|
| 83 |
+
await this.execAsync(cmd, onLog).catch(() => { });
|
| 84 |
}
|
| 85 |
onLog === null || onLog === void 0 ? void 0 : onLog(`[HF:STORAGE] Profile restoration complete.`);
|
| 86 |
}
|
|
|
|
| 97 |
return;
|
| 98 |
try {
|
| 99 |
onLog === null || onLog === void 0 ? void 0 : onLog(`[HF:STORAGE] Saving persistent profile to '${this.HF_DATASET_ID}'...`);
|
| 100 |
+
const persistenceEntries = this.getPersistenceEntries();
|
| 101 |
+
for (const entry of persistenceEntries) {
|
| 102 |
+
if (!fs_1.default.existsSync(entry.localPath))
|
|
|
|
|
|
|
| 103 |
continue;
|
| 104 |
+
const cmd = `(command -v huggingface-cli >/dev/null && huggingface-cli upload ${this.HF_DATASET_ID} ${entry.localPath} ${entry.datasetPath} --token ${this.HF_TOKEN} --message "CodeVerse Sync [${entry.datasetPath}]: ${new Date().toISOString()}" --exclude "node_modules/*" --exclude ".nix/*" --exclude ".direnv/*" --exclude ".cache/*") || (hf upload ${this.HF_DATASET_ID} ${entry.localPath} ${entry.datasetPath} --token ${this.HF_TOKEN})`;
|
| 105 |
+
onLog === null || onLog === void 0 ? void 0 : onLog(`[HF:STORAGE] Performing differential backup of ${entry.datasetPath}...`);
|
| 106 |
+
await this.execAsync(cmd, onLog).catch((err) => onLog === null || onLog === void 0 ? void 0 : onLog(`[WARN] Sync failed for ${entry.datasetPath}: ${err.message}`));
|
| 107 |
}
|
| 108 |
onLog === null || onLog === void 0 ? void 0 : onLog(`[HF:STORAGE] Profile backup successful.`);
|
| 109 |
}
|
dist/lib/idx/idx-engine.js
CHANGED
|
@@ -13,10 +13,10 @@ const child_process_1 = require("child_process");
|
|
| 13 |
*/
|
| 14 |
class IdxEngine {
|
| 15 |
static async hasCommand(command) {
|
| 16 |
-
const probeCommand = process.platform === 'win32' ? 'where' : '
|
| 17 |
-
const probeArgs =
|
| 18 |
return await new Promise((resolve) => {
|
| 19 |
-
const child = (0, child_process_1.spawn)(probeCommand, probeArgs, { shell:
|
| 20 |
child.on('close', (code) => resolve(code === 0));
|
| 21 |
child.on('error', () => resolve(false));
|
| 22 |
});
|
|
|
|
| 13 |
*/
|
| 14 |
class IdxEngine {
|
| 15 |
static async hasCommand(command) {
|
| 16 |
+
const probeCommand = process.platform === 'win32' ? 'where' : 'which';
|
| 17 |
+
const probeArgs = [command];
|
| 18 |
return await new Promise((resolve) => {
|
| 19 |
+
const child = (0, child_process_1.spawn)(probeCommand, probeArgs, { shell: false });
|
| 20 |
child.on('close', (code) => resolve(code === 0));
|
| 21 |
child.on('error', () => resolve(false));
|
| 22 |
});
|
dist/server.js
CHANGED
|
@@ -65,8 +65,8 @@ const constants_1 = require("./constants");
|
|
| 65 |
/**
|
| 66 |
* PRODUCTION HARDENING (April 2026): Force writable temp paths for HF Spaces.
|
| 67 |
*/
|
| 68 |
-
process.env.TMPDIR =
|
| 69 |
-
process.env.HF_HOME =
|
| 70 |
if (!process.env.HOME)
|
| 71 |
process.env.HOME = '/home/node';
|
| 72 |
const dev = process.env.NODE_ENV !== "production";
|
|
@@ -355,7 +355,7 @@ io.on("connection", (socket) => {
|
|
| 355 |
shell = pty.spawn(process.env.SHELL || (os_1.default.platform() === "win32" ? "powershell.exe" : "bash"), [], {
|
| 356 |
cols: cols || 80,
|
| 357 |
rows: rows || 24,
|
| 358 |
-
cwd:
|
| 359 |
env: process.env,
|
| 360 |
});
|
| 361 |
shell.onData((data) => socket.emit("terminal:data", data));
|
|
|
|
| 65 |
/**
|
| 66 |
* PRODUCTION HARDENING (April 2026): Force writable temp paths for HF Spaces.
|
| 67 |
*/
|
| 68 |
+
process.env.TMPDIR = env_config_1.ENV_CONFIG.TMPDIR;
|
| 69 |
+
process.env.HF_HOME = env_config_1.ENV_CONFIG.HF_HOME;
|
| 70 |
if (!process.env.HOME)
|
| 71 |
process.env.HOME = '/home/node';
|
| 72 |
const dev = process.env.NODE_ENV !== "production";
|
|
|
|
| 355 |
shell = pty.spawn(process.env.SHELL || (os_1.default.platform() === "win32" ? "powershell.exe" : "bash"), [], {
|
| 356 |
cols: cols || 80,
|
| 357 |
rows: rows || 24,
|
| 358 |
+
cwd: env_config_1.ENV_CONFIG.WORKSPACE_ROOT,
|
| 359 |
env: process.env,
|
| 360 |
});
|
| 361 |
shell.onData((data) => socket.emit("terminal:data", data));
|
lib/docker/manager.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
| 1 |
import fs from 'fs';
|
| 2 |
-
import { spawn, ChildProcess, execSync } from 'child_process';
|
| 3 |
import path from 'path';
|
| 4 |
import Docker from 'dockerode';
|
| 5 |
import { EventEmitter } from 'events';
|
|
@@ -46,6 +46,14 @@ interface WorkspaceRuntimePaths {
|
|
| 46 |
npmCachePath: string;
|
| 47 |
}
|
| 48 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 49 |
const SHORT_WORKSPACE_ID_LENGTH = 8;
|
| 50 |
const RUNTIME_ROOT_DIR_NAME = '.codeverse-runtime';
|
| 51 |
|
|
@@ -170,22 +178,74 @@ function findAvailablePort(): number {
|
|
| 170 |
return port;
|
| 171 |
}
|
| 172 |
|
| 173 |
-
function
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 174 |
const overrideBinary = process.env.CODE_SERVER_BIN;
|
| 175 |
if (overrideBinary) {
|
| 176 |
-
|
|
|
|
| 177 |
}
|
| 178 |
|
| 179 |
if (process.platform === 'win32') {
|
| 180 |
-
|
| 181 |
-
|
| 182 |
-
return { command:
|
| 183 |
-
} catch {
|
| 184 |
-
return { command: 'npx.cmd', args: ['--yes', 'code-server'], label: 'npx code-server' };
|
| 185 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 186 |
}
|
| 187 |
|
| 188 |
-
|
| 189 |
}
|
| 190 |
|
| 191 |
/**
|
|
@@ -337,6 +397,13 @@ async function performProvisioning(config: WorkspaceConfig): Promise<WorkspaceOp
|
|
| 337 |
const codeServerLaunch = resolveCodeServerLaunch();
|
| 338 |
const shellCommand = codeServerLaunch.command;
|
| 339 |
const args = codeServerLaunch.args;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 340 |
|
| 341 |
const baseArgs = [
|
| 342 |
'--auth', 'none',
|
|
@@ -356,11 +423,25 @@ async function performProvisioning(config: WorkspaceConfig): Promise<WorkspaceOp
|
|
| 356 |
delete spawnEnv.PORT;
|
| 357 |
delete spawnEnv.SERVER_PORT;
|
| 358 |
|
| 359 |
-
const
|
| 360 |
-
|
| 361 |
-
|
| 362 |
-
|
| 363 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 364 |
|
| 365 |
log(`Spawning VS Code Orchestrator via ${codeServerLaunch.label} (PID: ${child.pid})...`);
|
| 366 |
let childExited = false;
|
|
@@ -370,13 +451,13 @@ async function performProvisioning(config: WorkspaceConfig): Promise<WorkspaceOp
|
|
| 370 |
childFailureReason = `IDE_BINARY_FAILURE: ${err.message}`;
|
| 371 |
log(`[FATAL] IDE binary failure: ${err.message}`);
|
| 372 |
});
|
| 373 |
-
child.stdout.on('data', (data) => {
|
| 374 |
const out = data.toString().trim();
|
| 375 |
if (out.includes('listening on')) log(`[IDX:UP] ${out}`);
|
| 376 |
else if (out.length > 0) log(`[IDE:CORE] ${out}`);
|
| 377 |
});
|
| 378 |
|
| 379 |
-
child.stderr.on('data', (data) => {
|
| 380 |
const err = data.toString().trim();
|
| 381 |
if (err.length > 0) log(`[IDE:ERR] ${err}`);
|
| 382 |
});
|
|
@@ -398,7 +479,7 @@ async function performProvisioning(config: WorkspaceConfig): Promise<WorkspaceOp
|
|
| 398 |
while (attempts < 60) {
|
| 399 |
if (childExited) {
|
| 400 |
const rawFailureMessage = childFailureReason ?? 'IDE_PROCESS_EXITED_BEFORE_HANDSHAKE';
|
| 401 |
-
const failureMessage =
|
| 402 |
? `${rawFailureMessage}. code-server could not be bootstrapped via npx. Install code-server globally or set CODE_SERVER_BIN.`
|
| 403 |
: rawFailureMessage;
|
| 404 |
log(`[FATAL] IDE bootstrap aborted before handshake: ${failureMessage}`);
|
|
@@ -448,7 +529,7 @@ async function performProvisioning(config: WorkspaceConfig): Promise<WorkspaceOp
|
|
| 448 |
const error = e instanceof Error ? e.message : String(e);
|
| 449 |
log(`[FATAL] Provisioning pipeline collapsed: ${error}`);
|
| 450 |
nativeProcesses.delete(config.id);
|
| 451 |
-
const errResult = { success: false, error:
|
| 452 |
provisioningBus.emit(`error:${config.id}`, errResult);
|
| 453 |
return errResult;
|
| 454 |
}
|
|
|
|
| 1 |
import fs from 'fs';
|
| 2 |
+
import { spawn, ChildProcess, execFileSync, execSync } from 'child_process';
|
| 3 |
import path from 'path';
|
| 4 |
import Docker from 'dockerode';
|
| 5 |
import { EventEmitter } from 'events';
|
|
|
|
| 46 |
npmCachePath: string;
|
| 47 |
}
|
| 48 |
|
| 49 |
+
interface CodeServerLaunch {
|
| 50 |
+
command: string;
|
| 51 |
+
args: string[];
|
| 52 |
+
label: string;
|
| 53 |
+
usesNpx: boolean;
|
| 54 |
+
useShell: boolean;
|
| 55 |
+
}
|
| 56 |
+
|
| 57 |
const SHORT_WORKSPACE_ID_LENGTH = 8;
|
| 58 |
const RUNTIME_ROOT_DIR_NAME = '.codeverse-runtime';
|
| 59 |
|
|
|
|
| 178 |
return port;
|
| 179 |
}
|
| 180 |
|
| 181 |
+
function resolveExecutableOnPath(candidates: string[]): string | null {
|
| 182 |
+
const locatorCommand = process.platform === 'win32' ? 'where' : 'which';
|
| 183 |
+
|
| 184 |
+
for (const candidate of candidates) {
|
| 185 |
+
try {
|
| 186 |
+
const output = execFileSync(locatorCommand, [candidate], {
|
| 187 |
+
encoding: 'utf8',
|
| 188 |
+
stdio: ['ignore', 'pipe', 'ignore'],
|
| 189 |
+
});
|
| 190 |
+
const resolvedPath = output
|
| 191 |
+
.split(/\r?\n/)
|
| 192 |
+
.map((line) => line.trim())
|
| 193 |
+
.find(Boolean);
|
| 194 |
+
if (resolvedPath) {
|
| 195 |
+
return resolvedPath;
|
| 196 |
+
}
|
| 197 |
+
} catch {
|
| 198 |
+
// Fall through to the next candidate.
|
| 199 |
+
}
|
| 200 |
+
}
|
| 201 |
+
|
| 202 |
+
return null;
|
| 203 |
+
}
|
| 204 |
+
|
| 205 |
+
function quoteWindowsCmdArg(value: string): string {
|
| 206 |
+
if (value.length === 0) {
|
| 207 |
+
return '""';
|
| 208 |
+
}
|
| 209 |
+
|
| 210 |
+
if (!/[ \t"]/.test(value)) {
|
| 211 |
+
return value;
|
| 212 |
+
}
|
| 213 |
+
|
| 214 |
+
return `"${value.replace(/"/g, '""')}"`;
|
| 215 |
+
}
|
| 216 |
+
|
| 217 |
+
function resolveCodeServerLaunch(): CodeServerLaunch {
|
| 218 |
const overrideBinary = process.env.CODE_SERVER_BIN;
|
| 219 |
if (overrideBinary) {
|
| 220 |
+
const useShell = process.platform === 'win32' && /\.(cmd|bat)$/i.test(overrideBinary);
|
| 221 |
+
return { command: overrideBinary, args: [], label: overrideBinary, usesNpx: false, useShell };
|
| 222 |
}
|
| 223 |
|
| 224 |
if (process.platform === 'win32') {
|
| 225 |
+
const codeServerBinary = resolveExecutableOnPath(['code-server.exe', 'code-server']);
|
| 226 |
+
if (codeServerBinary) {
|
| 227 |
+
return { command: codeServerBinary, args: [], label: 'code-server', usesNpx: false, useShell: false };
|
|
|
|
|
|
|
| 228 |
}
|
| 229 |
+
|
| 230 |
+
const npxCliPath = path.join(path.dirname(process.execPath), 'node_modules', 'npm', 'bin', 'npx-cli.js');
|
| 231 |
+
if (fs.existsSync(npxCliPath)) {
|
| 232 |
+
return { command: process.execPath, args: [npxCliPath, '--yes', 'code-server'], label: 'node npx-cli.js code-server', usesNpx: true, useShell: false };
|
| 233 |
+
}
|
| 234 |
+
|
| 235 |
+
throw new Error('CODE_SERVER_BIN_NOT_FOUND');
|
| 236 |
+
}
|
| 237 |
+
|
| 238 |
+
const codeServerBinary = resolveExecutableOnPath(['code-server']);
|
| 239 |
+
if (codeServerBinary) {
|
| 240 |
+
return { command: codeServerBinary, args: [], label: 'code-server', usesNpx: false, useShell: false };
|
| 241 |
+
}
|
| 242 |
+
|
| 243 |
+
const npxBinary = resolveExecutableOnPath(['npx']);
|
| 244 |
+
if (npxBinary) {
|
| 245 |
+
return { command: npxBinary, args: ['--yes', 'code-server'], label: 'npx code-server', usesNpx: true, useShell: false };
|
| 246 |
}
|
| 247 |
|
| 248 |
+
throw new Error('CODE_SERVER_BIN_NOT_FOUND');
|
| 249 |
}
|
| 250 |
|
| 251 |
/**
|
|
|
|
| 397 |
const codeServerLaunch = resolveCodeServerLaunch();
|
| 398 |
const shellCommand = codeServerLaunch.command;
|
| 399 |
const args = codeServerLaunch.args;
|
| 400 |
+
const spawnCwd = (() => {
|
| 401 |
+
try {
|
| 402 |
+
return fs.realpathSync(workspacePath);
|
| 403 |
+
} catch {
|
| 404 |
+
return workspacePath;
|
| 405 |
+
}
|
| 406 |
+
})();
|
| 407 |
|
| 408 |
const baseArgs = [
|
| 409 |
'--auth', 'none',
|
|
|
|
| 423 |
delete spawnEnv.PORT;
|
| 424 |
delete spawnEnv.SERVER_PORT;
|
| 425 |
|
| 426 |
+
const launchArgs = [...args, ...baseArgs];
|
| 427 |
+
const launchCommand = codeServerLaunch.useShell ? (process.env.ComSpec || 'cmd.exe') : shellCommand;
|
| 428 |
+
const launchCommandArgs = codeServerLaunch.useShell
|
| 429 |
+
? ['/d', '/s', '/c', [quoteWindowsCmdArg(shellCommand), ...launchArgs.map(quoteWindowsCmdArg)].join(' ')]
|
| 430 |
+
: launchArgs;
|
| 431 |
+
|
| 432 |
+
log(`IDE launch prepared. Binary: ${shellCommand} | cwd: ${spawnCwd} | target: ${workspacePath}`);
|
| 433 |
+
|
| 434 |
+
let child: ChildProcess;
|
| 435 |
+
try {
|
| 436 |
+
child = spawn(launchCommand, launchCommandArgs, {
|
| 437 |
+
env: spawnEnv,
|
| 438 |
+
cwd: spawnCwd,
|
| 439 |
+
shell: false
|
| 440 |
+
});
|
| 441 |
+
} catch (error) {
|
| 442 |
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
| 443 |
+
throw new Error(`IDE_SPAWN_CONFIGURATION_REJECTED: ${errorMessage}`);
|
| 444 |
+
}
|
| 445 |
|
| 446 |
log(`Spawning VS Code Orchestrator via ${codeServerLaunch.label} (PID: ${child.pid})...`);
|
| 447 |
let childExited = false;
|
|
|
|
| 451 |
childFailureReason = `IDE_BINARY_FAILURE: ${err.message}`;
|
| 452 |
log(`[FATAL] IDE binary failure: ${err.message}`);
|
| 453 |
});
|
| 454 |
+
child.stdout?.on('data', (data) => {
|
| 455 |
const out = data.toString().trim();
|
| 456 |
if (out.includes('listening on')) log(`[IDX:UP] ${out}`);
|
| 457 |
else if (out.length > 0) log(`[IDE:CORE] ${out}`);
|
| 458 |
});
|
| 459 |
|
| 460 |
+
child.stderr?.on('data', (data) => {
|
| 461 |
const err = data.toString().trim();
|
| 462 |
if (err.length > 0) log(`[IDE:ERR] ${err}`);
|
| 463 |
});
|
|
|
|
| 479 |
while (attempts < 60) {
|
| 480 |
if (childExited) {
|
| 481 |
const rawFailureMessage = childFailureReason ?? 'IDE_PROCESS_EXITED_BEFORE_HANDSHAKE';
|
| 482 |
+
const failureMessage = codeServerLaunch.usesNpx
|
| 483 |
? `${rawFailureMessage}. code-server could not be bootstrapped via npx. Install code-server globally or set CODE_SERVER_BIN.`
|
| 484 |
: rawFailureMessage;
|
| 485 |
log(`[FATAL] IDE bootstrap aborted before handshake: ${failureMessage}`);
|
|
|
|
| 529 |
const error = e instanceof Error ? e.message : String(e);
|
| 530 |
log(`[FATAL] Provisioning pipeline collapsed: ${error}`);
|
| 531 |
nativeProcesses.delete(config.id);
|
| 532 |
+
const errResult = { success: false, error: `PROVISIONING_FAILED: ${error}` };
|
| 533 |
provisioningBus.emit(`error:${config.id}`, errResult);
|
| 534 |
return errResult;
|
| 535 |
}
|
lib/hf/storage.ts
CHANGED
|
@@ -16,17 +16,31 @@ export class HFStorage {
|
|
| 16 |
return Boolean(ENV_CONFIG.SPACE_ID && this.HF_TOKEN && this.HF_DATASET_ID && process.platform !== 'win32');
|
| 17 |
}
|
| 18 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
/**
|
| 20 |
* Internal helper for asynchronous execution with logging.
|
| 21 |
*/
|
| 22 |
private static async execAsync(command: string, onLog?: (msg: string) => void): Promise<void> {
|
| 23 |
return new Promise((resolve, reject) => {
|
| 24 |
-
const spawnEnv = {
|
| 25 |
-
...process.env,
|
| 26 |
HF_TOKEN: this.HF_TOKEN,
|
| 27 |
HF_HOME: '/tmp/.cache/huggingface',
|
| 28 |
TMPDIR: '/tmp',
|
| 29 |
-
PATH: `/home/node/.local/bin:/home/node/.nix-profile/bin:/usr/local/bin:/usr/bin:${process.env.PATH}`
|
| 30 |
};
|
| 31 |
const child = spawn('/bin/bash', ['-c', command], {
|
| 32 |
env: spawnEnv
|
|
@@ -63,19 +77,20 @@ export class HFStorage {
|
|
| 63 |
const tmpDir = '/tmp/hf-sync';
|
| 64 |
if (!fs.existsSync(tmpDir)) fs.mkdirSync(tmpDir, { recursive: true });
|
| 65 |
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
|
|
|
| 79 |
} catch (e: unknown) {
|
| 80 |
const errorMessage = e instanceof Error ? e.message : String(e);
|
| 81 |
onLog?.(`[ERROR] Profile restoration failed: ${errorMessage}`);
|
|
@@ -90,16 +105,14 @@ export class HFStorage {
|
|
| 90 |
|
| 91 |
try {
|
| 92 |
onLog?.(`[HF:STORAGE] Saving persistent profile to '${this.HF_DATASET_ID}'...`);
|
| 93 |
-
const
|
| 94 |
-
const persistDirs = ['w', '.vscode-server', '.config/code-server'];
|
| 95 |
|
| 96 |
-
for (const
|
| 97 |
-
|
| 98 |
-
if (!fs.existsSync(localPath)) continue;
|
| 99 |
|
| 100 |
-
const cmd = `(command -v huggingface-cli >/dev/null && huggingface-cli upload ${this.HF_DATASET_ID} ${localPath} ${
|
| 101 |
-
onLog?.(`[HF:STORAGE] Performing differential backup of ${
|
| 102 |
-
await this.execAsync(cmd, onLog).catch(err => onLog?.(`[WARN] Sync failed for ${
|
| 103 |
}
|
| 104 |
onLog?.(`[HF:STORAGE] Profile backup successful.`);
|
| 105 |
} catch (e: unknown) {
|
|
@@ -115,7 +128,7 @@ export class HFStorage {
|
|
| 115 |
static startAutoSave(intervalMs: number = 300000) {
|
| 116 |
if (this.autoSaveStarted || !this.isPersistenceRuntimeEnabled) return;
|
| 117 |
this.autoSaveStarted = true;
|
| 118 |
-
|
| 119 |
console.log(`[HF:STORAGE] Persistence heartbeat initialized (Interval: ${intervalMs}ms)`);
|
| 120 |
setInterval(async () => {
|
| 121 |
await this.syncToDataset((msg) => console.log(msg)).catch(() => {});
|
|
|
|
| 16 |
return Boolean(ENV_CONFIG.SPACE_ID && this.HF_TOKEN && this.HF_DATASET_ID && process.platform !== 'win32');
|
| 17 |
}
|
| 18 |
|
| 19 |
+
private static getPersistenceEntries(): Array<{ datasetPath: string; localPath: string }> {
|
| 20 |
+
const home = process.env.HOME || '/home/node';
|
| 21 |
+
const relativeWorkspaceRoot = path.relative(home, this.WORKSPACE_ROOT).replace(/\\/g, '/');
|
| 22 |
+
const workspaceDatasetPath = relativeWorkspaceRoot.startsWith('..')
|
| 23 |
+
? path.basename(this.WORKSPACE_ROOT)
|
| 24 |
+
: relativeWorkspaceRoot;
|
| 25 |
+
|
| 26 |
+
return [
|
| 27 |
+
{ datasetPath: workspaceDatasetPath, localPath: this.WORKSPACE_ROOT },
|
| 28 |
+
{ datasetPath: '.vscode-server', localPath: path.join(home, '.vscode-server') },
|
| 29 |
+
{ datasetPath: '.config/code-server', localPath: path.join(home, '.config', 'code-server') },
|
| 30 |
+
];
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
/**
|
| 34 |
* Internal helper for asynchronous execution with logging.
|
| 35 |
*/
|
| 36 |
private static async execAsync(command: string, onLog?: (msg: string) => void): Promise<void> {
|
| 37 |
return new Promise((resolve, reject) => {
|
| 38 |
+
const spawnEnv = {
|
| 39 |
+
...process.env,
|
| 40 |
HF_TOKEN: this.HF_TOKEN,
|
| 41 |
HF_HOME: '/tmp/.cache/huggingface',
|
| 42 |
TMPDIR: '/tmp',
|
| 43 |
+
PATH: `/home/node/.local/bin:/home/node/.nix-profile/bin:/usr/local/bin:/usr/bin:${process.env.PATH}`,
|
| 44 |
};
|
| 45 |
const child = spawn('/bin/bash', ['-c', command], {
|
| 46 |
env: spawnEnv
|
|
|
|
| 77 |
const tmpDir = '/tmp/hf-sync';
|
| 78 |
if (!fs.existsSync(tmpDir)) fs.mkdirSync(tmpDir, { recursive: true });
|
| 79 |
|
| 80 |
+
const home = process.env.HOME || '/home/node';
|
| 81 |
+
const persistenceEntries = this.getPersistenceEntries();
|
| 82 |
+
|
| 83 |
+
for (const entry of persistenceEntries) {
|
| 84 |
+
if (!fs.existsSync(entry.localPath)) {
|
| 85 |
+
fs.mkdirSync(entry.localPath, { recursive: true });
|
| 86 |
+
}
|
| 87 |
+
|
| 88 |
+
const cmd = `(command -v huggingface-cli >/dev/null && huggingface-cli download ${this.HF_DATASET_ID} --local-dir ${home} --include "${entry.datasetPath}/*" --token ${this.HF_TOKEN}) || (hf download ${this.HF_DATASET_ID} --local-dir ${home} --include "${entry.datasetPath}/*" --token ${this.HF_TOKEN})`;
|
| 89 |
+
onLog?.(`[HF:STORAGE] Restoring ${entry.datasetPath} from differential profile...`);
|
| 90 |
+
await this.execAsync(cmd, onLog).catch(() => {});
|
| 91 |
+
}
|
| 92 |
+
|
| 93 |
+
onLog?.(`[HF:STORAGE] Profile restoration complete.`);
|
| 94 |
} catch (e: unknown) {
|
| 95 |
const errorMessage = e instanceof Error ? e.message : String(e);
|
| 96 |
onLog?.(`[ERROR] Profile restoration failed: ${errorMessage}`);
|
|
|
|
| 105 |
|
| 106 |
try {
|
| 107 |
onLog?.(`[HF:STORAGE] Saving persistent profile to '${this.HF_DATASET_ID}'...`);
|
| 108 |
+
const persistenceEntries = this.getPersistenceEntries();
|
|
|
|
| 109 |
|
| 110 |
+
for (const entry of persistenceEntries) {
|
| 111 |
+
if (!fs.existsSync(entry.localPath)) continue;
|
|
|
|
| 112 |
|
| 113 |
+
const cmd = `(command -v huggingface-cli >/dev/null && huggingface-cli upload ${this.HF_DATASET_ID} ${entry.localPath} ${entry.datasetPath} --token ${this.HF_TOKEN} --message "CodeVerse Sync [${entry.datasetPath}]: ${new Date().toISOString()}" --exclude "node_modules/*" --exclude ".nix/*" --exclude ".direnv/*" --exclude ".cache/*") || (hf upload ${this.HF_DATASET_ID} ${entry.localPath} ${entry.datasetPath} --token ${this.HF_TOKEN})`;
|
| 114 |
+
onLog?.(`[HF:STORAGE] Performing differential backup of ${entry.datasetPath}...`);
|
| 115 |
+
await this.execAsync(cmd, onLog).catch((err: Error) => onLog?.(`[WARN] Sync failed for ${entry.datasetPath}: ${err.message}`));
|
| 116 |
}
|
| 117 |
onLog?.(`[HF:STORAGE] Profile backup successful.`);
|
| 118 |
} catch (e: unknown) {
|
|
|
|
| 128 |
static startAutoSave(intervalMs: number = 300000) {
|
| 129 |
if (this.autoSaveStarted || !this.isPersistenceRuntimeEnabled) return;
|
| 130 |
this.autoSaveStarted = true;
|
| 131 |
+
|
| 132 |
console.log(`[HF:STORAGE] Persistence heartbeat initialized (Interval: ${intervalMs}ms)`);
|
| 133 |
setInterval(async () => {
|
| 134 |
await this.syncToDataset((msg) => console.log(msg)).catch(() => {});
|
lib/idx/idx-engine.ts
CHANGED
|
@@ -17,11 +17,11 @@ export interface IdxConfig {
|
|
| 17 |
*/
|
| 18 |
export class IdxEngine {
|
| 19 |
private static async hasCommand(command: string): Promise<boolean> {
|
| 20 |
-
const probeCommand = process.platform === 'win32' ? 'where' : '
|
| 21 |
-
const probeArgs =
|
| 22 |
|
| 23 |
return await new Promise<boolean>((resolve) => {
|
| 24 |
-
const child = spawn(probeCommand, probeArgs, { shell:
|
| 25 |
child.on('close', (code) => resolve(code === 0));
|
| 26 |
child.on('error', () => resolve(false));
|
| 27 |
});
|
|
|
|
| 17 |
*/
|
| 18 |
export class IdxEngine {
|
| 19 |
private static async hasCommand(command: string): Promise<boolean> {
|
| 20 |
+
const probeCommand = process.platform === 'win32' ? 'where' : 'which';
|
| 21 |
+
const probeArgs = [command];
|
| 22 |
|
| 23 |
return await new Promise<boolean>((resolve) => {
|
| 24 |
+
const child = spawn(probeCommand, probeArgs, { shell: false });
|
| 25 |
child.on('close', (code) => resolve(code === 0));
|
| 26 |
child.on('error', () => resolve(false));
|
| 27 |
});
|
lib/system/vitals.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
export interface SystemVitals {
|
| 2 |
+
rss: number;
|
| 3 |
+
heapUsed: number;
|
| 4 |
+
heapTotal: number;
|
| 5 |
+
loadAvg: [number, number, number];
|
| 6 |
+
uptime: number;
|
| 7 |
+
systemMemory: {
|
| 8 |
+
free: number;
|
| 9 |
+
total: number;
|
| 10 |
+
};
|
| 11 |
+
nodeVersion: string;
|
| 12 |
+
}
|
| 13 |
+
|
| 14 |
+
export interface SystemVitalsResponse {
|
| 15 |
+
success: true;
|
| 16 |
+
vitals: SystemVitals;
|
| 17 |
+
}
|
server.ts
CHANGED
|
@@ -24,15 +24,15 @@ import { getNativeWorkspacePort, isNativeWorkspaceRunning, prewarmWorkspace, rec
|
|
| 24 |
import { initDb } from "./lib/db/schema";
|
| 25 |
import { client as dbClient } from "./lib/db";
|
| 26 |
import { HFStorage } from "./lib/hf/storage";
|
| 27 |
-
import { validateEnvironment } from "./lib/env-config";
|
| 28 |
import httpProxy from "http-proxy";
|
| 29 |
import { APP_CONFIG, INFRA_CONFIG, UI_STRINGS } from "./constants";
|
| 30 |
|
| 31 |
/**
|
| 32 |
* PRODUCTION HARDENING (April 2026): Force writable temp paths for HF Spaces.
|
| 33 |
*/
|
| 34 |
-
process.env.TMPDIR =
|
| 35 |
-
process.env.HF_HOME =
|
| 36 |
if (!process.env.HOME) process.env.HOME = '/home/node';
|
| 37 |
|
| 38 |
const dev = process.env.NODE_ENV !== "production";
|
|
@@ -358,7 +358,7 @@ io.on("connection", (socket) => {
|
|
| 358 |
shell = pty.spawn(process.env.SHELL || (os.platform() === "win32" ? "powershell.exe" : "bash"), [], {
|
| 359 |
cols: cols || 80,
|
| 360 |
rows: rows || 24,
|
| 361 |
-
cwd:
|
| 362 |
env: process.env as Record<string, string>,
|
| 363 |
});
|
| 364 |
shell.onData((data: string) => socket.emit("terminal:data", data));
|
|
|
|
| 24 |
import { initDb } from "./lib/db/schema";
|
| 25 |
import { client as dbClient } from "./lib/db";
|
| 26 |
import { HFStorage } from "./lib/hf/storage";
|
| 27 |
+
import { ENV_CONFIG, validateEnvironment } from "./lib/env-config";
|
| 28 |
import httpProxy from "http-proxy";
|
| 29 |
import { APP_CONFIG, INFRA_CONFIG, UI_STRINGS } from "./constants";
|
| 30 |
|
| 31 |
/**
|
| 32 |
* PRODUCTION HARDENING (April 2026): Force writable temp paths for HF Spaces.
|
| 33 |
*/
|
| 34 |
+
process.env.TMPDIR = ENV_CONFIG.TMPDIR;
|
| 35 |
+
process.env.HF_HOME = ENV_CONFIG.HF_HOME;
|
| 36 |
if (!process.env.HOME) process.env.HOME = '/home/node';
|
| 37 |
|
| 38 |
const dev = process.env.NODE_ENV !== "production";
|
|
|
|
| 358 |
shell = pty.spawn(process.env.SHELL || (os.platform() === "win32" ? "powershell.exe" : "bash"), [], {
|
| 359 |
cols: cols || 80,
|
| 360 |
rows: rows || 24,
|
| 361 |
+
cwd: ENV_CONFIG.WORKSPACE_ROOT,
|
| 362 |
env: process.env as Record<string, string>,
|
| 363 |
});
|
| 364 |
shell.onData((data: string) => socket.emit("terminal:data", data));
|