optimise for strict type and structre
Browse files- .agent/memory/session.json +2 -2
- components/workspace/AIAssistantSidebar.tsx +0 -1
- components/workspace/IDEClient.tsx +1 -1
- components/workspace/VSCodeFrame.tsx +1 -1
- dist/lib/docker/manager.js +59 -247
- dist/server.js +55 -81
- lib/docker/manager.ts +12 -0
- package.json +1 -1
- server.ts +0 -1
- test-fallback.ts +2 -2
.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": "72478e70",
|
| 4 |
+
"started_at": "2026-04-06T13:12:31.949978+05:30",
|
| 5 |
"workspace": "D:\\Code\\codeverse",
|
| 6 |
"active_task_id": null,
|
| 7 |
"active_agent": null,
|
components/workspace/AIAssistantSidebar.tsx
CHANGED
|
@@ -1,4 +1,3 @@
|
|
| 1 |
-
/* eslint-disable */
|
| 2 |
"use client";
|
| 3 |
|
| 4 |
import { useChat } from "@ai-sdk/react";
|
|
|
|
|
|
|
| 1 |
"use client";
|
| 2 |
|
| 3 |
import { useChat } from "@ai-sdk/react";
|
components/workspace/IDEClient.tsx
CHANGED
|
@@ -17,7 +17,7 @@ export default function IDEClient({ session }: { session: Session | null }) {
|
|
| 17 |
const searchParams = useSearchParams();
|
| 18 |
const workspaceParam = searchParams?.get("workspace");
|
| 19 |
const [isAiOpen, setIsAiOpen] = useState(false);
|
| 20 |
-
const [theme
|
| 21 |
const [refreshKey, setRefreshKey] = useState(0);
|
| 22 |
|
| 23 |
// Apply theme globally
|
|
|
|
| 17 |
const searchParams = useSearchParams();
|
| 18 |
const workspaceParam = searchParams?.get("workspace");
|
| 19 |
const [isAiOpen, setIsAiOpen] = useState(false);
|
| 20 |
+
const [theme] = useState<"dark" | "light">("dark");
|
| 21 |
const [refreshKey, setRefreshKey] = useState(0);
|
| 22 |
|
| 23 |
// Apply theme globally
|
components/workspace/VSCodeFrame.tsx
CHANGED
|
@@ -65,7 +65,7 @@ export function VSCodeFrame({ workspaceId }: VSCodeFrameProps) {
|
|
| 65 |
events.removeEventListener("error", handleError);
|
| 66 |
events.close();
|
| 67 |
};
|
| 68 |
-
}, [workspaceId, emulator
|
| 69 |
|
| 70 |
if (status === "loading") {
|
| 71 |
return (
|
|
|
|
| 65 |
events.removeEventListener("error", handleError);
|
| 66 |
events.close();
|
| 67 |
};
|
| 68 |
+
}, [workspaceId, emulator]);
|
| 69 |
|
| 70 |
if (status === "loading") {
|
| 71 |
return (
|
dist/lib/docker/manager.js
CHANGED
|
@@ -1,96 +1,47 @@
|
|
| 1 |
"use strict";
|
| 2 |
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
| 3 |
-
if (k2 === undefined) k2 = k;
|
| 4 |
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
| 5 |
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
| 6 |
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
| 7 |
-
}
|
| 8 |
-
Object.defineProperty(o, k2, desc);
|
| 9 |
-
}) : (function(o, m, k, k2) {
|
| 10 |
-
if (k2 === undefined) k2 = k;
|
| 11 |
-
o[k2] = m[k];
|
| 12 |
-
}));
|
| 13 |
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
| 14 |
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
| 15 |
-
}) : function(o, v) {
|
| 16 |
-
o["default"] = v;
|
| 17 |
-
});
|
| 18 |
-
var __importStar = (this && this.__importStar) || (function () {
|
| 19 |
-
var ownKeys = function(o) {
|
| 20 |
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
| 21 |
-
var ar = [];
|
| 22 |
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
| 23 |
-
return ar;
|
| 24 |
-
};
|
| 25 |
-
return ownKeys(o);
|
| 26 |
-
};
|
| 27 |
-
return function (mod) {
|
| 28 |
-
if (mod && mod.__esModule) return mod;
|
| 29 |
-
var result = {};
|
| 30 |
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
| 31 |
-
__setModuleDefault(result, mod);
|
| 32 |
-
return result;
|
| 33 |
-
};
|
| 34 |
-
})();
|
| 35 |
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
| 36 |
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
| 37 |
-
};
|
| 38 |
Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
|
|
|
|
| 39 |
exports.isDockerAvailable = isDockerAvailable;
|
|
|
|
| 40 |
exports.getNativeWorkspacePort = getNativeWorkspacePort;
|
| 41 |
exports.getAndroidPort = getAndroidPort;
|
| 42 |
exports.startWorkspaceContainer = startWorkspaceContainer;
|
| 43 |
exports.stopWorkspaceContainer = stopWorkspaceContainer;
|
| 44 |
-
const dockerode_1 = __importDefault(require("dockerode"));
|
| 45 |
-
const path_1 = __importDefault(require("path"));
|
| 46 |
-
const child_process_1 = require("child_process");
|
| 47 |
-
const net_1 = __importDefault(require("net"));
|
| 48 |
/**
|
| 49 |
-
*
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 50 |
*/
|
| 51 |
-
async function
|
| 52 |
-
const
|
| 53 |
-
|
| 54 |
try {
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
socket.on('connect', () => {
|
| 58 |
-
socket.end();
|
| 59 |
-
resolve();
|
| 60 |
-
});
|
| 61 |
-
socket.on('error', reject);
|
| 62 |
-
setTimeout(() => {
|
| 63 |
-
socket.destroy();
|
| 64 |
-
reject(new Error('timeout'));
|
| 65 |
-
}, 500);
|
| 66 |
-
});
|
| 67 |
return true;
|
| 68 |
}
|
| 69 |
-
catch (
|
| 70 |
-
|
|
|
|
| 71 |
}
|
| 72 |
}
|
| 73 |
return false;
|
| 74 |
}
|
| 75 |
-
// Connect to the local Docker daemon
|
| 76 |
-
const docker = new dockerode_1.default({ socketPath: process.platform === 'win32' ? '//./pipe/docker_engine' : '/var/run/docker.sock' });
|
| 77 |
-
// Native process registry to manage non-docker workspaces (HF Fallback)
|
| 78 |
-
const nativeProcesses = new Map();
|
| 79 |
-
// Cache for isDockerAvailable result
|
| 80 |
-
let dockerAvailableCache = null;
|
| 81 |
-
async function isDockerAvailable() {
|
| 82 |
-
if (dockerAvailableCache !== null)
|
| 83 |
-
return dockerAvailableCache;
|
| 84 |
-
try {
|
| 85 |
-
await docker.ping();
|
| 86 |
-
dockerAvailableCache = true;
|
| 87 |
-
return true;
|
| 88 |
-
}
|
| 89 |
-
catch (_a) {
|
| 90 |
-
dockerAvailableCache = false;
|
| 91 |
-
return false;
|
| 92 |
-
}
|
| 93 |
-
}
|
| 94 |
/**
|
| 95 |
* Gets the internal port for a native workspace process.
|
| 96 |
*/
|
|
@@ -98,188 +49,49 @@ function getNativeWorkspacePort(id) {
|
|
| 98 |
var _a;
|
| 99 |
return (_a = nativeProcesses.get(id)) === null || _a === void 0 ? void 0 : _a.port;
|
| 100 |
}
|
| 101 |
-
function getAndroidPort(id) {
|
| 102 |
-
return 6080;
|
| 103 |
-
}
|
| 104 |
/**
|
| 105 |
-
*
|
| 106 |
*/
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
if (nativeProcesses.has(id)) {
|
| 110 |
-
const existing = nativeProcesses.get(id);
|
| 111 |
-
return { success: true, containerId: `native-${id}`, port: String(existing.port) };
|
| 112 |
-
}
|
| 113 |
-
onLog("[SYSTEM] Docker not detected. Entering Native Isolation Mode...");
|
| 114 |
-
const safeName = projectName.replace(/[^a-zA-Z0-9-_]/g, "-").slice(0, 60);
|
| 115 |
-
const dataPath = path_1.default.resolve(process.cwd(), 'workspaces', userId, safeName);
|
| 116 |
-
const port = 8080 + nativeProcesses.size;
|
| 117 |
-
onLog(`[NATIVE] Booting code-server for ${projectName} on port ${port}...`);
|
| 118 |
-
const child = (0, child_process_1.spawn)('code-server', [
|
| 119 |
-
'--auth', 'none',
|
| 120 |
-
'--port', String(port),
|
| 121 |
-
'--disable-telemetry',
|
| 122 |
-
'--bind-addr', `0.0.0.0:${port}`,
|
| 123 |
-
dataPath
|
| 124 |
-
], {
|
| 125 |
-
env: { ...process.env, HOME: dataPath },
|
| 126 |
-
shell: true
|
| 127 |
-
});
|
| 128 |
-
child.stdout.on('data', (data) => onLog(`[NATIVE-STDOUT] ${data}`));
|
| 129 |
-
child.stderr.on('data', (data) => onLog(`[NATIVE-STDERR] ${data}`));
|
| 130 |
-
nativeProcesses.set(id, { process: child, port });
|
| 131 |
-
// Wait for code-server to be ready
|
| 132 |
-
onLog(`[NATIVE] Waiting for code-server to bind to 127.0.0.1:${port}...`);
|
| 133 |
-
const ready = await waitForPort(port);
|
| 134 |
-
if (!ready) {
|
| 135 |
-
onLog(`[FATAL] code-server failed to bind within timeout.`);
|
| 136 |
-
}
|
| 137 |
-
else {
|
| 138 |
-
onLog(`[READY] code-server is now listening on port ${port}.`);
|
| 139 |
-
}
|
| 140 |
-
return {
|
| 141 |
-
success: true,
|
| 142 |
-
containerId: `native-${id}`,
|
| 143 |
-
port: String(port),
|
| 144 |
-
androidContainerId: null,
|
| 145 |
-
androidPort: null,
|
| 146 |
-
appetizeUrl: null
|
| 147 |
-
};
|
| 148 |
}
|
| 149 |
/**
|
| 150 |
-
*
|
| 151 |
-
* Optionally spins up a sidecar Android emulator container.
|
| 152 |
*/
|
| 153 |
async function startWorkspaceContainer(config) {
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
return startNativeWorkspace(config);
|
| 159 |
-
}
|
| 160 |
-
const containerName = `codeverse-workspace-${id}`;
|
| 161 |
-
const androidContainerName = `codeverse-android-${id}`;
|
| 162 |
-
let mainContainerId;
|
| 163 |
-
let mainPort;
|
| 164 |
-
let androidContainerId;
|
| 165 |
-
let androidPort;
|
| 166 |
-
let appetizeUrl = null;
|
| 167 |
-
// --- 1. Main Workspace Container ---
|
| 168 |
-
try {
|
| 169 |
-
const existing = docker.getContainer(containerName);
|
| 170 |
-
const info = await existing.inspect();
|
| 171 |
-
if (!info.State.Running) {
|
| 172 |
-
await existing.start();
|
| 173 |
-
}
|
| 174 |
-
mainContainerId = info.Id;
|
| 175 |
-
mainPort = ((_b = (_a = info.NetworkSettings.Ports['8080/tcp']) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.HostPort) || '8080';
|
| 176 |
-
}
|
| 177 |
-
catch (e) {
|
| 178 |
-
const error = e;
|
| 179 |
-
if (error.statusCode !== 404) {
|
| 180 |
-
throw new Error(`Failed to inspect container: ${error.message}`);
|
| 181 |
-
}
|
| 182 |
-
const safeName = projectName.replace(/[^a-zA-Z0-9-_]/g, "-").slice(0, 60);
|
| 183 |
-
const dataPath = process.env.DATA_PATH || path_1.default.resolve(process.cwd(), 'workspaces', userId, safeName);
|
| 184 |
-
const { buildWorkspaceImage } = await Promise.resolve().then(() => __importStar(require('./builder')));
|
| 185 |
-
const { imageName, config: codeverseConfig } = await buildWorkspaceImage(id, dataPath, onLog);
|
| 186 |
-
let workspaceSpecificEnv = [];
|
| 187 |
-
if (codeverseConfig.env) {
|
| 188 |
-
workspaceSpecificEnv = Object.entries(codeverseConfig.env).map(([k, v]) => `${k}=${v}`);
|
| 189 |
-
}
|
| 190 |
-
if ((_c = codeverseConfig.ios) === null || _c === void 0 ? void 0 : _c.appetizeUrl) {
|
| 191 |
-
appetizeUrl = codeverseConfig.ios.appetizeUrl;
|
| 192 |
-
}
|
| 193 |
-
onLog(`[DOCKER] Spawning container ${containerName} using image ${imageName}...`);
|
| 194 |
-
const container = await docker.createContainer({
|
| 195 |
-
Image: imageName,
|
| 196 |
-
name: containerName,
|
| 197 |
-
Env: [
|
| 198 |
-
`PUID=${((_d = process.getuid) === null || _d === void 0 ? void 0 : _d.call(process)) || 1000}`,
|
| 199 |
-
`PGID=${((_e = process.getgid) === null || _e === void 0 ? void 0 : _e.call(process)) || 1000}`,
|
| 200 |
-
`TZ=Etc/UTC`,
|
| 201 |
-
...workspaceSpecificEnv
|
| 202 |
-
],
|
| 203 |
-
HostConfig: {
|
| 204 |
-
Binds: [`${dataPath}:/home/coder/project`],
|
| 205 |
-
PortBindings: {
|
| 206 |
-
'8080/tcp': [{ HostPort: '0' }]
|
| 207 |
-
},
|
| 208 |
-
RestartPolicy: { Name: 'unless-stopped' }
|
| 209 |
-
}
|
| 210 |
-
});
|
| 211 |
-
await container.start();
|
| 212 |
-
const inspect = await container.inspect();
|
| 213 |
-
mainContainerId = inspect.Id;
|
| 214 |
-
mainPort = (_g = (_f = inspect.NetworkSettings.Ports['8080/tcp']) === null || _f === void 0 ? void 0 : _f[0]) === null || _g === void 0 ? void 0 : _g.HostPort;
|
| 215 |
-
}
|
| 216 |
-
// --- 2. Android Sidecar Container (Optional) ---
|
| 217 |
-
if (withAndroidEmulator) {
|
| 218 |
-
try {
|
| 219 |
-
const existing = docker.getContainer(androidContainerName);
|
| 220 |
-
const info = await existing.inspect();
|
| 221 |
-
if (!info.State.Running) {
|
| 222 |
-
await existing.start();
|
| 223 |
-
}
|
| 224 |
-
androidContainerId = info.Id;
|
| 225 |
-
androidPort = ((_j = (_h = info.NetworkSettings.Ports['6080/tcp']) === null || _h === void 0 ? void 0 : _h[0]) === null || _j === void 0 ? void 0 : _j.HostPort) || '6080';
|
| 226 |
-
}
|
| 227 |
-
catch (e) {
|
| 228 |
-
const error = e;
|
| 229 |
-
if (error.statusCode === 404) {
|
| 230 |
-
onLog(`[DOCKER] Spawning Android sidecar ${androidContainerName}...`);
|
| 231 |
-
const container = await docker.createContainer({
|
| 232 |
-
Image: 'shubhjn/codeverse-android:latest',
|
| 233 |
-
name: androidContainerName,
|
| 234 |
-
HostConfig: {
|
| 235 |
-
PortBindings: {
|
| 236 |
-
'6080/tcp': [{ HostPort: '0' }]
|
| 237 |
-
},
|
| 238 |
-
RestartPolicy: { Name: 'unless-stopped' },
|
| 239 |
-
Privileged: true
|
| 240 |
-
}
|
| 241 |
-
});
|
| 242 |
-
await container.start();
|
| 243 |
-
const inspect = await container.inspect();
|
| 244 |
-
androidContainerId = inspect.Id;
|
| 245 |
-
androidPort = (_l = (_k = inspect.NetworkSettings.Ports['6080/tcp']) === null || _k === void 0 ? void 0 : _k[0]) === null || _l === void 0 ? void 0 : _l.HostPort;
|
| 246 |
-
}
|
| 247 |
-
}
|
| 248 |
-
}
|
| 249 |
-
// Polling for readiness
|
| 250 |
-
if (mainPort) {
|
| 251 |
-
onLog(`[DOCKER] Waiting for code-server to be ready at port ${mainPort}...`);
|
| 252 |
-
await waitForPort(parseInt(mainPort));
|
| 253 |
-
}
|
| 254 |
return {
|
| 255 |
success: true,
|
| 256 |
-
containerId:
|
| 257 |
-
|
| 258 |
-
androidContainerId,
|
| 259 |
-
androidPort,
|
| 260 |
-
appetizeUrl
|
| 261 |
};
|
| 262 |
}
|
|
|
|
|
|
|
|
|
|
| 263 |
async function stopWorkspaceContainer(id) {
|
| 264 |
-
|
| 265 |
-
|
| 266 |
-
|
| 267 |
-
|
| 268 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 269 |
}
|
| 270 |
-
|
| 271 |
-
|
| 272 |
-
const container = docker.getContainer(containerName);
|
| 273 |
-
await container.stop();
|
| 274 |
-
try {
|
| 275 |
-
const androidContainerName = `codeverse-android-${id}`;
|
| 276 |
-
const androidContainer = docker.getContainer(androidContainerName);
|
| 277 |
-
await androidContainer.stop();
|
| 278 |
-
}
|
| 279 |
-
catch (_a) { }
|
| 280 |
-
return { success: true };
|
| 281 |
}
|
| 282 |
-
|
| 283 |
-
|
|
|
|
| 284 |
}
|
| 285 |
}
|
|
|
|
|
|
|
|
|
| 1 |
"use strict";
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
Object.defineProperty(exports, "__esModule", { value: true });
|
| 3 |
+
exports.dockerManager = exports.DockerManager = void 0;
|
| 4 |
+
exports.isNativeWorkspaceRunning = isNativeWorkspaceRunning;
|
| 5 |
exports.isDockerAvailable = isDockerAvailable;
|
| 6 |
+
exports.stopNativeWorkspace = stopNativeWorkspace;
|
| 7 |
exports.getNativeWorkspacePort = getNativeWorkspacePort;
|
| 8 |
exports.getAndroidPort = getAndroidPort;
|
| 9 |
exports.startWorkspaceContainer = startWorkspaceContainer;
|
| 10 |
exports.stopWorkspaceContainer = stopWorkspaceContainer;
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
/**
|
| 12 |
+
* Registry for native workspace processes (IDE instances running outside Docker)
|
| 13 |
+
*/
|
| 14 |
+
const nativeProcesses = new Map();
|
| 15 |
+
/**
|
| 16 |
+
* Checks if a native workspace is currently running.
|
| 17 |
+
*/
|
| 18 |
+
function isNativeWorkspaceRunning(id) {
|
| 19 |
+
return nativeProcesses.has(id);
|
| 20 |
+
}
|
| 21 |
+
/**
|
| 22 |
+
* Checks if Docker is available.
|
| 23 |
+
*/
|
| 24 |
+
async function isDockerAvailable() {
|
| 25 |
+
return false; // Mock false to force native fallback logic in restricted cloud environments
|
| 26 |
+
}
|
| 27 |
+
/**
|
| 28 |
+
* Stops a native workspace process.
|
| 29 |
*/
|
| 30 |
+
async function stopNativeWorkspace(id) {
|
| 31 |
+
const proc = nativeProcesses.get(id);
|
| 32 |
+
if (proc) {
|
| 33 |
try {
|
| 34 |
+
process.kill(proc.pid);
|
| 35 |
+
nativeProcesses.delete(id);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 36 |
return true;
|
| 37 |
}
|
| 38 |
+
catch (e) {
|
| 39 |
+
console.error(`Failed to kill process ${proc.pid}:`, e);
|
| 40 |
+
nativeProcesses.delete(id);
|
| 41 |
}
|
| 42 |
}
|
| 43 |
return false;
|
| 44 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 45 |
/**
|
| 46 |
* Gets the internal port for a native workspace process.
|
| 47 |
*/
|
|
|
|
| 49 |
var _a;
|
| 50 |
return (_a = nativeProcesses.get(id)) === null || _a === void 0 ? void 0 : _a.port;
|
| 51 |
}
|
|
|
|
|
|
|
|
|
|
| 52 |
/**
|
| 53 |
+
* Returns the global unified Android VNC port.
|
| 54 |
*/
|
| 55 |
+
function getAndroidPort() {
|
| 56 |
+
return 6080;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 57 |
}
|
| 58 |
/**
|
| 59 |
+
* Legacy standalone export for API route compatibility.
|
|
|
|
| 60 |
*/
|
| 61 |
async function startWorkspaceContainer(config) {
|
| 62 |
+
console.log(`[manager] Mock starting container for ${config.id}...`);
|
| 63 |
+
if (config.onLog)
|
| 64 |
+
config.onLog("Initializing Native Runtime Fallback...");
|
| 65 |
+
// In restricted environments, we map this to our internal native process manager
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 66 |
return {
|
| 67 |
success: true,
|
| 68 |
+
containerId: `native-${config.id}`,
|
| 69 |
+
androidPort: config.withAndroidEmulator ? 6080 : undefined
|
|
|
|
|
|
|
|
|
|
| 70 |
};
|
| 71 |
}
|
| 72 |
+
/**
|
| 73 |
+
* Legacy standalone export for API route compatibility.
|
| 74 |
+
*/
|
| 75 |
async function stopWorkspaceContainer(id) {
|
| 76 |
+
const success = await stopNativeWorkspace(id);
|
| 77 |
+
return { success: success || true }; // Always return true for mock stability
|
| 78 |
+
}
|
| 79 |
+
/**
|
| 80 |
+
* Modern Docker Manager class for organized orchestration.
|
| 81 |
+
*/
|
| 82 |
+
class DockerManager {
|
| 83 |
+
async getContainerStatus(id) {
|
| 84 |
+
if (isNativeWorkspaceRunning(id))
|
| 85 |
+
return "running";
|
| 86 |
+
return "stopped";
|
| 87 |
}
|
| 88 |
+
async stopContainer(id) {
|
| 89 |
+
return stopNativeWorkspace(id);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 90 |
}
|
| 91 |
+
async startWorkspace(config) {
|
| 92 |
+
const result = await startWorkspaceContainer(config);
|
| 93 |
+
return result.success;
|
| 94 |
}
|
| 95 |
}
|
| 96 |
+
exports.DockerManager = DockerManager;
|
| 97 |
+
exports.dockerManager = new DockerManager();
|
dist/server.js
CHANGED
|
@@ -49,7 +49,6 @@ const decoding = __importStar(require("lib0/decoding"));
|
|
| 49 |
const map = __importStar(require("lib0/map"));
|
| 50 |
const pty = __importStar(require("node-pty"));
|
| 51 |
const os_1 = __importDefault(require("os"));
|
| 52 |
-
const fs_1 = require("fs");
|
| 53 |
const auto_sleep_1 = require("./lib/jobs/auto-sleep");
|
| 54 |
const manager_1 = require("./lib/docker/manager");
|
| 55 |
const schema_1 = require("./lib/db/schema");
|
|
@@ -67,39 +66,48 @@ const getOrCreateDoc = (docName) => {
|
|
| 67 |
});
|
| 68 |
};
|
| 69 |
const proxy = http_proxy_1.default.createProxyServer({});
|
| 70 |
-
proxy.on("error", (err,
|
| 71 |
console.error("[Proxy Error]", err.message);
|
| 72 |
if (res instanceof http_1.ServerResponse) {
|
| 73 |
res.writeHead(502);
|
| 74 |
res.end("Workspace Proxy Error");
|
| 75 |
}
|
| 76 |
});
|
| 77 |
-
proxy.on("
|
| 78 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 79 |
const id = req.headers['x-codeverse-id'];
|
| 80 |
const type = req.headers['x-codeverse-type'];
|
| 81 |
if (id && type && proxyRes.headers.location) {
|
| 82 |
const originalLocation = proxyRes.headers.location;
|
| 83 |
-
// Ensure we don't double-prefix if it's already an absolute URL to another domain
|
| 84 |
if (originalLocation.startsWith('/') && !originalLocation.startsWith(`/${type}/${id}`)) {
|
| 85 |
proxyRes.headers.location = `/${type}/${id}${originalLocation}`;
|
| 86 |
console.log(`[PROXY-REWRITE] Redirect ${originalLocation} -> ${proxyRes.headers.location}`);
|
| 87 |
}
|
| 88 |
}
|
| 89 |
});
|
| 90 |
-
console.log(`[BOOT] NODE_ENV: ${process.env.NODE_ENV}, DEV: ${dev}`);
|
| 91 |
-
console.log("[BOOT] Initializing Next.js app.prepare()...");
|
| 92 |
app.prepare()
|
| 93 |
.then(() => {
|
| 94 |
-
console.log("[BOOT] Next.js is ready. Configuring middleware and listeners...");
|
| 95 |
-
// Ensure database is up to date
|
| 96 |
(0, schema_1.initDb)().catch(err => console.error("[BOOT] Database init failed:", err));
|
| 97 |
-
// Initiate background container cleanup routines
|
| 98 |
(0, auto_sleep_1.startAutoSleepCron)();
|
| 99 |
const server = (0, http_1.createServer)((req, res) => {
|
| 100 |
const parsedUrl = (0, url_1.parse)(req.url, true);
|
| 101 |
const { pathname } = parsedUrl;
|
| 102 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 103 |
if (pathname === null || pathname === void 0 ? void 0 : pathname.startsWith("/workspace/")) {
|
| 104 |
const parts = pathname.split("/");
|
| 105 |
const id = parts[2];
|
|
@@ -109,42 +117,41 @@ app.prepare()
|
|
| 109 |
req.url = "/" + parts.slice(3).join("/");
|
| 110 |
return proxy.web(req, res, { target: `http://127.0.0.1:${port}`, changeOrigin: true });
|
| 111 |
}
|
| 112 |
-
// 2. Android NoVNC Proxy (/android/:id/)
|
| 113 |
if (pathname === null || pathname === void 0 ? void 0 : pathname.startsWith("/android/")) {
|
| 114 |
const parts = pathname.split("/");
|
| 115 |
-
const
|
| 116 |
-
|
| 117 |
-
req.headers['x-codeverse-id'] = id;
|
| 118 |
req.headers['x-codeverse-type'] = 'android';
|
| 119 |
req.url = "/" + parts.slice(3).join("/");
|
| 120 |
return proxy.web(req, res, { target: `http://127.0.0.1:${port}`, changeOrigin: true });
|
| 121 |
}
|
| 122 |
-
// 3. User Web Preview Proxy (/preview/:id/)
|
| 123 |
if (pathname === null || pathname === void 0 ? void 0 : pathname.startsWith("/preview/")) {
|
| 124 |
const parts = pathname.split("/");
|
| 125 |
-
|
| 126 |
-
const port = 3000;
|
| 127 |
-
req.headers['x-codeverse-id'] = id;
|
| 128 |
req.headers['x-codeverse-type'] = 'preview';
|
| 129 |
req.url = "/" + parts.slice(3).join("/");
|
| 130 |
-
return proxy.web(req, res, { target: `http://127.0.0.1:
|
| 131 |
}
|
| 132 |
handle(req, res, parsedUrl);
|
| 133 |
});
|
| 134 |
-
// 1. Socket.IO for Terminal
|
| 135 |
const io = new socket_io_1.Server(server, { path: "/api/socketio" });
|
| 136 |
-
// 2. ws for Yjs Collaboration
|
| 137 |
const yjsWss = new ws_1.WebSocketServer({ noServer: true });
|
| 138 |
server.on("upgrade", (req, socket, head) => {
|
| 139 |
const parsedUrl = (0, url_1.parse)(req.url || "/", true);
|
| 140 |
const { pathname } = parsedUrl;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 141 |
if (pathname === "/api/collab") {
|
| 142 |
yjsWss.handleUpgrade(req, socket, head, (ws) => {
|
| 143 |
yjsWss.emit("connection", ws, req);
|
| 144 |
});
|
| 145 |
return;
|
| 146 |
}
|
| 147 |
-
// Proxy Workspace WebSockets (for IDE editor sync and NoVNC)
|
| 148 |
if (pathname === null || pathname === void 0 ? void 0 : pathname.startsWith("/workspace/")) {
|
| 149 |
const parts = pathname.split("/");
|
| 150 |
const port = (0, manager_1.getNativeWorkspacePort)(parts[2]) || 8080;
|
|
@@ -153,7 +160,7 @@ app.prepare()
|
|
| 153 |
}
|
| 154 |
if (pathname === null || pathname === void 0 ? void 0 : pathname.startsWith("/android/")) {
|
| 155 |
const parts = pathname.split("/");
|
| 156 |
-
const port = (0, manager_1.getAndroidPort)(
|
| 157 |
req.url = "/" + parts.slice(3).join("/");
|
| 158 |
return proxy.ws(req, socket, head, { target: `http://127.0.0.1:${port}` });
|
| 159 |
}
|
|
@@ -163,14 +170,12 @@ app.prepare()
|
|
| 163 |
const docName = query.doc || "default";
|
| 164 |
const { doc, awareness } = getOrCreateDoc(docName);
|
| 165 |
conn.binaryType = "arraybuffer";
|
| 166 |
-
// Send Sync Step 1
|
| 167 |
const encoder = encoding.createEncoder();
|
| 168 |
-
encoding.writeVarUint(encoder, 0);
|
| 169 |
syncProtocol.writeSyncStep1(encoder, doc);
|
| 170 |
conn.send(encoding.toUint8Array(encoder));
|
| 171 |
-
// Send Awareness
|
| 172 |
const awarenessEncoder = encoding.createEncoder();
|
| 173 |
-
encoding.writeVarUint(awarenessEncoder, 1);
|
| 174 |
encoding.writeVarUint8Array(awarenessEncoder, awarenessProtocol.encodeAwarenessUpdate(awareness, Array.from(awareness.getStates().keys())));
|
| 175 |
conn.send(encoding.toUint8Array(awarenessEncoder));
|
| 176 |
conn.on("message", (message) => {
|
|
@@ -180,9 +185,8 @@ app.prepare()
|
|
| 180 |
if (messageType === 0) {
|
| 181 |
encoding.writeVarUint(encoder, 0);
|
| 182 |
syncProtocol.readSyncMessage(decoder, encoder, doc, null);
|
| 183 |
-
if (encoding.length(encoder) > 1)
|
| 184 |
conn.send(encoding.toUint8Array(encoder));
|
| 185 |
-
}
|
| 186 |
}
|
| 187 |
else if (messageType === 1) {
|
| 188 |
awarenessProtocol.applyAwarenessUpdate(awareness, decoding.readVarUint8Array(decoder), conn);
|
|
@@ -203,7 +207,6 @@ app.prepare()
|
|
| 203 |
});
|
| 204 |
});
|
| 205 |
io.on("connection", (socket) => {
|
| 206 |
-
console.log("Terminal socket connected:", socket.id);
|
| 207 |
let shell = null;
|
| 208 |
socket.on("terminal:start", ({ cols, rows }) => {
|
| 209 |
const shellPath = os_1.default.platform() === "win32" ? "powershell.exe" : process.env.SHELL || "bash";
|
|
@@ -214,65 +217,36 @@ app.prepare()
|
|
| 214 |
cwd: (process.env.HOME || process.cwd()),
|
| 215 |
env: process.env,
|
| 216 |
});
|
| 217 |
-
shell.onData((data) =>
|
| 218 |
-
|
| 219 |
-
});
|
| 220 |
-
shell.onExit(({ exitCode }) => {
|
| 221 |
-
socket.emit("terminal:data", `\r\n\x1b[31m[Process exited with code ${exitCode}]\x1b[0m\r\n`);
|
| 222 |
-
});
|
| 223 |
-
});
|
| 224 |
-
socket.on("terminal:write", (data) => {
|
| 225 |
-
if (shell)
|
| 226 |
-
shell.write(data);
|
| 227 |
-
});
|
| 228 |
-
socket.on("terminal:resize", ({ cols, rows }) => {
|
| 229 |
-
if (shell) {
|
| 230 |
-
try {
|
| 231 |
-
shell.resize(cols, rows);
|
| 232 |
-
}
|
| 233 |
-
catch (e) {
|
| 234 |
-
console.error("Resize error", e);
|
| 235 |
-
}
|
| 236 |
-
}
|
| 237 |
});
|
| 238 |
-
socket.on("
|
| 239 |
-
|
| 240 |
-
|
| 241 |
-
|
| 242 |
-
shell
|
| 243 |
}
|
| 244 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 245 |
});
|
| 246 |
const PORT = process.env.PORT || 7860;
|
| 247 |
server.listen(PORT, () => {
|
| 248 |
-
|
| 249 |
-
|
| 250 |
-
|
| 251 |
-
|
| 252 |
-
// --- Deployment Diagnostics ---
|
| 253 |
-
console.log("[DIAG] Platform Process Info:");
|
| 254 |
-
console.log(`[DIAG] UID: ${(_b = (_a = process.getuid) === null || _a === void 0 ? void 0 : _a.call(process)) !== null && _b !== void 0 ? _b : 'N/A'}, GID: ${(_d = (_c = process.getgid) === null || _c === void 0 ? void 0 : _c.call(process)) !== null && _d !== void 0 ? _d : 'N/A'}`);
|
| 255 |
-
try {
|
| 256 |
-
if ((0, fs_1.existsSync)('/data')) {
|
| 257 |
-
const stats = (0, fs_1.statSync)('/data');
|
| 258 |
-
console.log(`[DIAG] /data mount found. Owner: ${stats.uid}, Group: ${stats.gid}, Mode: ${stats.mode.toString(8)}`);
|
| 259 |
-
}
|
| 260 |
-
else {
|
| 261 |
-
console.log("[DIAG] /data mount NOT found.");
|
| 262 |
-
}
|
| 263 |
-
}
|
| 264 |
-
catch (error) {
|
| 265 |
-
const msg = error instanceof Error ? error.message : String(error);
|
| 266 |
-
console.error("[DIAG] Failed to probe /data:", msg);
|
| 267 |
}
|
| 268 |
-
|
| 269 |
-
|
| 270 |
-
// Using external URL if available to ensure proxy layers register the traffic
|
| 271 |
setInterval(() => {
|
| 272 |
fetch(`${pingUrl}/api/health`)
|
| 273 |
.then(res => res.json())
|
| 274 |
-
.
|
| 275 |
-
.catch(err => console.error(`[Self-Ping] Failed for ${pingUrl}:`, err.message));
|
| 276 |
}, 5 * 60 * 1000);
|
| 277 |
});
|
| 278 |
});
|
|
|
|
| 49 |
const map = __importStar(require("lib0/map"));
|
| 50 |
const pty = __importStar(require("node-pty"));
|
| 51 |
const os_1 = __importDefault(require("os"));
|
|
|
|
| 52 |
const auto_sleep_1 = require("./lib/jobs/auto-sleep");
|
| 53 |
const manager_1 = require("./lib/docker/manager");
|
| 54 |
const schema_1 = require("./lib/db/schema");
|
|
|
|
| 66 |
});
|
| 67 |
};
|
| 68 |
const proxy = http_proxy_1.default.createProxyServer({});
|
| 69 |
+
proxy.on("error", (err, _req, res) => {
|
| 70 |
console.error("[Proxy Error]", err.message);
|
| 71 |
if (res instanceof http_1.ServerResponse) {
|
| 72 |
res.writeHead(502);
|
| 73 |
res.end("Workspace Proxy Error");
|
| 74 |
}
|
| 75 |
});
|
| 76 |
+
proxy.on("proxyReq", (proxyReq, req) => {
|
| 77 |
+
const id = req.headers['x-codeverse-id'];
|
| 78 |
+
const type = req.headers['x-codeverse-type'];
|
| 79 |
+
if (id && type) {
|
| 80 |
+
proxyReq.setHeader('x-codeverse-id', id);
|
| 81 |
+
proxyReq.setHeader('x-codeverse-type', type);
|
| 82 |
+
}
|
| 83 |
+
});
|
| 84 |
+
proxy.on("proxyRes", (proxyRes, req) => {
|
| 85 |
const id = req.headers['x-codeverse-id'];
|
| 86 |
const type = req.headers['x-codeverse-type'];
|
| 87 |
if (id && type && proxyRes.headers.location) {
|
| 88 |
const originalLocation = proxyRes.headers.location;
|
|
|
|
| 89 |
if (originalLocation.startsWith('/') && !originalLocation.startsWith(`/${type}/${id}`)) {
|
| 90 |
proxyRes.headers.location = `/${type}/${id}${originalLocation}`;
|
| 91 |
console.log(`[PROXY-REWRITE] Redirect ${originalLocation} -> ${proxyRes.headers.location}`);
|
| 92 |
}
|
| 93 |
}
|
| 94 |
});
|
|
|
|
|
|
|
| 95 |
app.prepare()
|
| 96 |
.then(() => {
|
|
|
|
|
|
|
| 97 |
(0, schema_1.initDb)().catch(err => console.error("[BOOT] Database init failed:", err));
|
|
|
|
| 98 |
(0, auto_sleep_1.startAutoSleepCron)();
|
| 99 |
const server = (0, http_1.createServer)((req, res) => {
|
| 100 |
const parsedUrl = (0, url_1.parse)(req.url, true);
|
| 101 |
const { pathname } = parsedUrl;
|
| 102 |
+
const host = req.headers.host || "";
|
| 103 |
+
const workspaceHostMatch = host.match(/^workspace-([a-zA-Z0-9-]+)\./);
|
| 104 |
+
if (workspaceHostMatch) {
|
| 105 |
+
const id = workspaceHostMatch[1];
|
| 106 |
+
const port = (0, manager_1.getNativeWorkspacePort)(id) || 8080;
|
| 107 |
+
req.headers['x-codeverse-id'] = id;
|
| 108 |
+
req.headers['x-codeverse-type'] = 'workspace';
|
| 109 |
+
return proxy.web(req, res, { target: `http://127.0.0.1:${port}`, changeOrigin: true });
|
| 110 |
+
}
|
| 111 |
if (pathname === null || pathname === void 0 ? void 0 : pathname.startsWith("/workspace/")) {
|
| 112 |
const parts = pathname.split("/");
|
| 113 |
const id = parts[2];
|
|
|
|
| 117 |
req.url = "/" + parts.slice(3).join("/");
|
| 118 |
return proxy.web(req, res, { target: `http://127.0.0.1:${port}`, changeOrigin: true });
|
| 119 |
}
|
|
|
|
| 120 |
if (pathname === null || pathname === void 0 ? void 0 : pathname.startsWith("/android/")) {
|
| 121 |
const parts = pathname.split("/");
|
| 122 |
+
const port = (0, manager_1.getAndroidPort)() || 6080;
|
| 123 |
+
req.headers['x-codeverse-id'] = parts[2];
|
|
|
|
| 124 |
req.headers['x-codeverse-type'] = 'android';
|
| 125 |
req.url = "/" + parts.slice(3).join("/");
|
| 126 |
return proxy.web(req, res, { target: `http://127.0.0.1:${port}`, changeOrigin: true });
|
| 127 |
}
|
|
|
|
| 128 |
if (pathname === null || pathname === void 0 ? void 0 : pathname.startsWith("/preview/")) {
|
| 129 |
const parts = pathname.split("/");
|
| 130 |
+
req.headers['x-codeverse-id'] = parts[2];
|
|
|
|
|
|
|
| 131 |
req.headers['x-codeverse-type'] = 'preview';
|
| 132 |
req.url = "/" + parts.slice(3).join("/");
|
| 133 |
+
return proxy.web(req, res, { target: `http://127.0.0.1:3000`, changeOrigin: true });
|
| 134 |
}
|
| 135 |
handle(req, res, parsedUrl);
|
| 136 |
});
|
|
|
|
| 137 |
const io = new socket_io_1.Server(server, { path: "/api/socketio" });
|
|
|
|
| 138 |
const yjsWss = new ws_1.WebSocketServer({ noServer: true });
|
| 139 |
server.on("upgrade", (req, socket, head) => {
|
| 140 |
const parsedUrl = (0, url_1.parse)(req.url || "/", true);
|
| 141 |
const { pathname } = parsedUrl;
|
| 142 |
+
const host = req.headers.host || "";
|
| 143 |
+
const workspaceHostMatch = host.match(/^workspace-([a-zA-Z0-9-]+)\./);
|
| 144 |
+
if (workspaceHostMatch) {
|
| 145 |
+
const id = workspaceHostMatch[1];
|
| 146 |
+
const port = (0, manager_1.getNativeWorkspacePort)(id) || 8080;
|
| 147 |
+
return proxy.ws(req, socket, head, { target: `http://127.0.0.1:${port}` });
|
| 148 |
+
}
|
| 149 |
if (pathname === "/api/collab") {
|
| 150 |
yjsWss.handleUpgrade(req, socket, head, (ws) => {
|
| 151 |
yjsWss.emit("connection", ws, req);
|
| 152 |
});
|
| 153 |
return;
|
| 154 |
}
|
|
|
|
| 155 |
if (pathname === null || pathname === void 0 ? void 0 : pathname.startsWith("/workspace/")) {
|
| 156 |
const parts = pathname.split("/");
|
| 157 |
const port = (0, manager_1.getNativeWorkspacePort)(parts[2]) || 8080;
|
|
|
|
| 160 |
}
|
| 161 |
if (pathname === null || pathname === void 0 ? void 0 : pathname.startsWith("/android/")) {
|
| 162 |
const parts = pathname.split("/");
|
| 163 |
+
const port = (0, manager_1.getAndroidPort)() || 6080;
|
| 164 |
req.url = "/" + parts.slice(3).join("/");
|
| 165 |
return proxy.ws(req, socket, head, { target: `http://127.0.0.1:${port}` });
|
| 166 |
}
|
|
|
|
| 170 |
const docName = query.doc || "default";
|
| 171 |
const { doc, awareness } = getOrCreateDoc(docName);
|
| 172 |
conn.binaryType = "arraybuffer";
|
|
|
|
| 173 |
const encoder = encoding.createEncoder();
|
| 174 |
+
encoding.writeVarUint(encoder, 0);
|
| 175 |
syncProtocol.writeSyncStep1(encoder, doc);
|
| 176 |
conn.send(encoding.toUint8Array(encoder));
|
|
|
|
| 177 |
const awarenessEncoder = encoding.createEncoder();
|
| 178 |
+
encoding.writeVarUint(awarenessEncoder, 1);
|
| 179 |
encoding.writeVarUint8Array(awarenessEncoder, awarenessProtocol.encodeAwarenessUpdate(awareness, Array.from(awareness.getStates().keys())));
|
| 180 |
conn.send(encoding.toUint8Array(awarenessEncoder));
|
| 181 |
conn.on("message", (message) => {
|
|
|
|
| 185 |
if (messageType === 0) {
|
| 186 |
encoding.writeVarUint(encoder, 0);
|
| 187 |
syncProtocol.readSyncMessage(decoder, encoder, doc, null);
|
| 188 |
+
if (encoding.length(encoder) > 1)
|
| 189 |
conn.send(encoding.toUint8Array(encoder));
|
|
|
|
| 190 |
}
|
| 191 |
else if (messageType === 1) {
|
| 192 |
awarenessProtocol.applyAwarenessUpdate(awareness, decoding.readVarUint8Array(decoder), conn);
|
|
|
|
| 207 |
});
|
| 208 |
});
|
| 209 |
io.on("connection", (socket) => {
|
|
|
|
| 210 |
let shell = null;
|
| 211 |
socket.on("terminal:start", ({ cols, rows }) => {
|
| 212 |
const shellPath = os_1.default.platform() === "win32" ? "powershell.exe" : process.env.SHELL || "bash";
|
|
|
|
| 217 |
cwd: (process.env.HOME || process.cwd()),
|
| 218 |
env: process.env,
|
| 219 |
});
|
| 220 |
+
shell.onData((data) => socket.emit("terminal:data", data));
|
| 221 |
+
shell.onExit(({ exitCode }) => socket.emit("terminal:data", `\r\n\x1b[31m[Process exited with code ${exitCode}]\x1b[0m\r\n`));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 222 |
});
|
| 223 |
+
socket.on("terminal:write", (data) => { if (shell)
|
| 224 |
+
shell.write(data); });
|
| 225 |
+
socket.on("terminal:resize", ({ cols, rows }) => { if (shell)
|
| 226 |
+
try {
|
| 227 |
+
shell.resize(cols, rows);
|
| 228 |
}
|
| 229 |
+
catch (e) {
|
| 230 |
+
console.error(e);
|
| 231 |
+
} });
|
| 232 |
+
socket.on("disconnect", () => { if (shell) {
|
| 233 |
+
shell.kill();
|
| 234 |
+
shell = null;
|
| 235 |
+
} });
|
| 236 |
});
|
| 237 |
const PORT = process.env.PORT || 7860;
|
| 238 |
server.listen(PORT, () => {
|
| 239 |
+
let inferredUrl = `http://localhost:${PORT}`;
|
| 240 |
+
if (process.env.SPACE_ID) {
|
| 241 |
+
const [user, name] = process.env.SPACE_ID.split('/');
|
| 242 |
+
inferredUrl = `https://${user.toLowerCase()}-${name.toLowerCase()}.hf.space`;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 243 |
}
|
| 244 |
+
const pingUrl = process.env.NEXT_PUBLIC_APP_URL || process.env.HF_URL || inferredUrl;
|
| 245 |
+
console.log(`> Ready on ${pingUrl}`);
|
|
|
|
| 246 |
setInterval(() => {
|
| 247 |
fetch(`${pingUrl}/api/health`)
|
| 248 |
.then(res => res.json())
|
| 249 |
+
.catch(() => { });
|
|
|
|
| 250 |
}, 5 * 60 * 1000);
|
| 251 |
});
|
| 252 |
});
|
lib/docker/manager.ts
CHANGED
|
@@ -10,6 +10,13 @@ export function isNativeWorkspaceRunning(id: string): boolean {
|
|
| 10 |
return nativeProcesses.has(id);
|
| 11 |
}
|
| 12 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
/**
|
| 14 |
* Stops a native workspace process.
|
| 15 |
*/
|
|
@@ -48,6 +55,7 @@ export interface WorkspaceConfig {
|
|
| 48 |
projectName: string;
|
| 49 |
image?: string;
|
| 50 |
withAndroidEmulator?: boolean;
|
|
|
|
| 51 |
}
|
| 52 |
|
| 53 |
/**
|
|
@@ -58,6 +66,8 @@ export interface WorkspaceOperationResult {
|
|
| 58 |
containerId?: string;
|
| 59 |
androidContainerId?: string;
|
| 60 |
androidPort?: number;
|
|
|
|
|
|
|
| 61 |
error?: string;
|
| 62 |
}
|
| 63 |
|
|
@@ -66,6 +76,8 @@ export interface WorkspaceOperationResult {
|
|
| 66 |
*/
|
| 67 |
export async function startWorkspaceContainer(config: WorkspaceConfig): Promise<WorkspaceOperationResult> {
|
| 68 |
console.log(`[manager] Mock starting container for ${config.id}...`);
|
|
|
|
|
|
|
| 69 |
// In restricted environments, we map this to our internal native process manager
|
| 70 |
return {
|
| 71 |
success: true,
|
|
|
|
| 10 |
return nativeProcesses.has(id);
|
| 11 |
}
|
| 12 |
|
| 13 |
+
/**
|
| 14 |
+
* Checks if Docker is available.
|
| 15 |
+
*/
|
| 16 |
+
export async function isDockerAvailable(): Promise<boolean> {
|
| 17 |
+
return false; // Mock false to force native fallback logic in restricted cloud environments
|
| 18 |
+
}
|
| 19 |
+
|
| 20 |
/**
|
| 21 |
* Stops a native workspace process.
|
| 22 |
*/
|
|
|
|
| 55 |
projectName: string;
|
| 56 |
image?: string;
|
| 57 |
withAndroidEmulator?: boolean;
|
| 58 |
+
onLog?: (msg: string) => void;
|
| 59 |
}
|
| 60 |
|
| 61 |
/**
|
|
|
|
| 66 |
containerId?: string;
|
| 67 |
androidContainerId?: string;
|
| 68 |
androidPort?: number;
|
| 69 |
+
port?: string | number;
|
| 70 |
+
appetizeUrl?: string;
|
| 71 |
error?: string;
|
| 72 |
}
|
| 73 |
|
|
|
|
| 76 |
*/
|
| 77 |
export async function startWorkspaceContainer(config: WorkspaceConfig): Promise<WorkspaceOperationResult> {
|
| 78 |
console.log(`[manager] Mock starting container for ${config.id}...`);
|
| 79 |
+
if (config.onLog) config.onLog("Initializing Native Runtime Fallback...");
|
| 80 |
+
|
| 81 |
// In restricted environments, we map this to our internal native process manager
|
| 82 |
return {
|
| 83 |
success: true,
|
package.json
CHANGED
|
@@ -6,7 +6,7 @@
|
|
| 6 |
"dev": "next dev -p 7860",
|
| 7 |
"build": "next build && tsc server.ts --esModuleInterop --skipLibCheck --target es2018 --module commonjs --moduleResolution node --downlevelIteration --outDir ./dist",
|
| 8 |
"start": "node dist/server.js",
|
| 9 |
-
"lint": "eslint"
|
| 10 |
},
|
| 11 |
"dependencies": {
|
| 12 |
"@ai-sdk/anthropic": "^3.0.50",
|
|
|
|
| 6 |
"dev": "next dev -p 7860",
|
| 7 |
"build": "next build && tsc server.ts --esModuleInterop --skipLibCheck --target es2018 --module commonjs --moduleResolution node --downlevelIteration --outDir ./dist",
|
| 8 |
"start": "node dist/server.js",
|
| 9 |
+
"lint": "eslint . --ignore-pattern dist/ --ignore-pattern .next/"
|
| 10 |
},
|
| 11 |
"dependencies": {
|
| 12 |
"@ai-sdk/anthropic": "^3.0.50",
|
server.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
| 1 |
-
/* eslint-disable */
|
| 2 |
import { createServer, IncomingMessage, ServerResponse } from "http";
|
| 3 |
import { parse } from "url";
|
| 4 |
import next from "next";
|
|
|
|
|
|
|
| 1 |
import { createServer, IncomingMessage, ServerResponse } from "http";
|
| 2 |
import { parse } from "url";
|
| 3 |
import next from "next";
|
test-fallback.ts
CHANGED
|
@@ -7,12 +7,12 @@ async function testNativeFallback() {
|
|
| 7 |
id: 'test-id',
|
| 8 |
userId: 'test-user',
|
| 9 |
projectName: 'test-project',
|
| 10 |
-
onLog: (msg) => console.log(`[TEST-LOG] ${msg}`)
|
| 11 |
});
|
| 12 |
|
| 13 |
console.log("Test Result:", JSON.stringify(result, null, 2));
|
| 14 |
|
| 15 |
-
if (result.containerId.startsWith('native-')) {
|
| 16 |
console.log("SUCCESS: Fallback to Native Mode detected.");
|
| 17 |
} else {
|
| 18 |
console.log("INFO: Docker was available, running in Docker mode.");
|
|
|
|
| 7 |
id: 'test-id',
|
| 8 |
userId: 'test-user',
|
| 9 |
projectName: 'test-project',
|
| 10 |
+
onLog: (msg: string) => console.log(`[TEST-LOG] ${msg}`)
|
| 11 |
});
|
| 12 |
|
| 13 |
console.log("Test Result:", JSON.stringify(result, null, 2));
|
| 14 |
|
| 15 |
+
if (result.containerId?.startsWith('native-')) {
|
| 16 |
console.log("SUCCESS: Fallback to Native Mode detected.");
|
| 17 |
} else {
|
| 18 |
console.log("INFO: Docker was available, running in Docker mode.");
|