shubhjn commited on
Commit
6686367
ยท
1 Parent(s): d07b9e0

fix 503 workspace lock

Browse files
dist/lib/docker/manager.js CHANGED
@@ -43,6 +43,8 @@ exports.pendingProvisioning = new Map();
43
  function isNativeWorkspaceRunning(id) {
44
  if (exports.nativeProcesses.has(id))
45
  return true;
 
 
46
  // Prefix fallback for reconnected sessions
47
  return Array.from(exports.nativeProcesses.keys()).some(k => id.startsWith(k));
48
  }
@@ -174,7 +176,14 @@ async function performProvisioning(config) {
174
  fs_1.default.writeFileSync(path_1.default.join(workspacePath, '.codeverse-id'), config.id);
175
  log(`Allocated isolated filesystem segment: ${config.id.slice(0, 8)}`);
176
  }
177
- // 2. IDX Engine: Sync Environment
 
 
 
 
 
 
 
178
  const idxConfig = idx_engine_1.IdxEngine.getIdxConfig(workspacePath);
179
  log(`Declarative config detected (Packages: ${idxConfig.packages.length}). Initializing synchronization...`);
180
  await idx_engine_1.IdxEngine.syncNixEnvironment(workspacePath, idxConfig, (msg) => log(msg));
@@ -186,8 +195,6 @@ async function performProvisioning(config) {
186
  }
187
  fs_1.default.writeFileSync(flagPath, new Date().toISOString());
188
  }
189
- // 4. Identify Target Port
190
- const port = findAvailablePort();
191
  // 5. Spawn code-server
192
  const shellCommand = process.platform === 'win32' ? 'npx' : 'code-server';
193
  const args = process.platform === 'win32' ? ['code-server'] : [];
@@ -223,8 +230,9 @@ async function performProvisioning(config) {
223
  });
224
  child.on('close', (code, signal) => {
225
  log(`[IDE:EXIT] IDE process died with code ${code} (Signal: ${signal})`);
 
226
  });
227
- // 6. Register in active pool
228
  exports.nativeProcesses.set(config.id, { pid: child.pid, port, process: child });
229
  // 7. Handshake Loop
230
  let attempts = 0;
 
43
  function isNativeWorkspaceRunning(id) {
44
  if (exports.nativeProcesses.has(id))
45
  return true;
46
+ if (exports.pendingProvisioning.has(id))
47
+ return true;
48
  // Prefix fallback for reconnected sessions
49
  return Array.from(exports.nativeProcesses.keys()).some(k => id.startsWith(k));
50
  }
 
176
  fs_1.default.writeFileSync(path_1.default.join(workspacePath, '.codeverse-id'), config.id);
177
  log(`Allocated isolated filesystem segment: ${config.id.slice(0, 8)}`);
178
  }
179
+ // 2. Register in active pool (EARLY REGISTRATION: satisfy proxy health checks)
180
+ const port = findAvailablePort();
181
+ exports.nativeProcesses.set(config.id, {
182
+ pid: -1, // PID not yet available
183
+ port,
184
+ process: { kill: () => true, pid: -1 }
185
+ });
186
+ // 3. IDX Engine: Sync Environment
187
  const idxConfig = idx_engine_1.IdxEngine.getIdxConfig(workspacePath);
188
  log(`Declarative config detected (Packages: ${idxConfig.packages.length}). Initializing synchronization...`);
189
  await idx_engine_1.IdxEngine.syncNixEnvironment(workspacePath, idxConfig, (msg) => log(msg));
 
195
  }
196
  fs_1.default.writeFileSync(flagPath, new Date().toISOString());
197
  }
 
 
198
  // 5. Spawn code-server
199
  const shellCommand = process.platform === 'win32' ? 'npx' : 'code-server';
200
  const args = process.platform === 'win32' ? ['code-server'] : [];
 
230
  });
231
  child.on('close', (code, signal) => {
232
  log(`[IDE:EXIT] IDE process died with code ${code} (Signal: ${signal})`);
233
+ exports.nativeProcesses.delete(config.id);
234
  });
235
+ // 6. Update Registry with real Process
236
  exports.nativeProcesses.set(config.id, { pid: child.pid, port, process: child });
237
  // 7. Handshake Loop
238
  let attempts = 0;
dist/lib/idx/idx-engine.js CHANGED
@@ -150,7 +150,8 @@ class IdxEngine {
150
  ...process.env,
151
  HOME: workspacePath,
152
  NIX_CONFIG: 'experimental-features = nix-command flakes'
153
- }
 
154
  });
155
  child.stdout.on('data', (data) => log(data.toString().trim()));
156
  child.stderr.on('data', (data) => log(`[INFO] ${data.toString().trim()}`));
 
150
  ...process.env,
151
  HOME: workspacePath,
152
  NIX_CONFIG: 'experimental-features = nix-command flakes'
153
+ },
154
+ timeout: 300000 // 5-minute safety timeout
155
  });
156
  child.stdout.on('data', (data) => log(data.toString().trim()));
157
  child.stderr.on('data', (data) => log(`[INFO] ${data.toString().trim()}`));
dist/server.js CHANGED
@@ -205,8 +205,8 @@ const server = (0, http_1.createServer)((req, res) => {
205
  const workspaceHostMatch = host.match(/^workspace-([a-zA-Z0-9-]+)\./);
206
  const id = workspaceHostMatch ? workspaceHostMatch[1] : ((pathname === null || pathname === void 0 ? void 0 : pathname.startsWith("/workspace/")) ? pathname.split("/")[2] : null);
207
  if (id) {
208
- const isReady = (0, manager_1.isNativeWorkspaceRunning)(id);
209
- if (isReady) {
210
  const port = (0, manager_1.getNativeWorkspacePort)(id) || 8080;
211
  req.headers['x-codeverse-id'] = id;
212
  req.headers['x-codeverse-type'] = 'workspace';
@@ -219,26 +219,30 @@ const server = (0, http_1.createServer)((req, res) => {
219
  return proxy.web(req, res, { target: `http://127.0.0.1:${port}`, changeOrigin: true });
220
  }
221
  else if (!(pathname === null || pathname === void 0 ? void 0 : pathname.startsWith("/api/"))) {
222
- // ๐Ÿš‘ WORKSPACE BOOTING FALLBACK: Prevent Next.js 404 while workspace is initializing
223
  res.writeHead(503, { 'Content-Type': 'text/html', 'Retry-After': '5' });
224
  return res.end(`
225
  <html>
226
  <head>
227
- <title>CodeVerse | Booting Workspace</title>
228
  <style>
229
  body { background: #09090b; color: #71717a; font-family: sans-serif; display: flex; align-items: center; justify-content: center; height: 100vh; margin: 0; }
230
- .container { text-align: center; border: 1px solid #27272a; padding: 2rem; border-radius: 1rem; background: #111113; }
231
- .spinner { width: 40px; height: 40px; border: 3px solid #3f3f46; border-top-color: #3b82f6; border-radius: 50%; animation: spin 1s linear infinite; margin: 0 auto 1.5rem; }
232
- h1 { color: #f4f4f5; font-size: 1.25rem; }
 
 
 
233
  @keyframes spin { to { transform: rotate(360deg); } }
234
  </style>
235
- <script>setTimeout(() => window.location.reload(), 3000);</script>
236
  </head>
237
  <body>
238
  <div class="container">
239
  <div class="spinner"></div>
240
- <h1>Workspace is Booting</h1>
241
- <p>Preparing your agentic session...</p>
 
242
  </div>
243
  </body>
244
  </html>
@@ -250,7 +254,7 @@ const server = (0, http_1.createServer)((req, res) => {
250
  });
251
  // Setup Sockets
252
  const io = new socket_io_1.Server(server, { path: "/api/socketio" });
253
- const yjsWss = new ws_1.WebSocketServer({ noServer: true });
254
  // Start Listening Immediately
255
  server.listen(PORT, HOST, () => {
256
  console.log('----------------------------------------------------');
@@ -287,13 +291,13 @@ server.listen(PORT, HOST, () => {
287
  server.on("upgrade", (req, socket, head) => {
288
  const { pathname } = new URL(req.url || "/", `http://${req.headers.host}`);
289
  if (pathname === "/api/collab") {
290
- yjsWss.handleUpgrade(req, socket, head, (ws) => {
291
- yjsWss.emit("connection", ws, req);
292
  });
293
  }
294
  });
295
- yjsWss.on("connection", (conn, request) => {
296
- const { doc, awareness } = getOrCreateDoc(new URL(request.url || "/", "http://l").searchParams.get('doc') || "default");
297
  conn.binaryType = "arraybuffer";
298
  const encoder = encoding.createEncoder();
299
  encoding.writeVarUint(encoder, 0);
 
205
  const workspaceHostMatch = host.match(/^workspace-([a-zA-Z0-9-]+)\./);
206
  const id = workspaceHostMatch ? workspaceHostMatch[1] : ((pathname === null || pathname === void 0 ? void 0 : pathname.startsWith("/workspace/")) ? pathname.split("/")[2] : null);
207
  if (id) {
208
+ const isRunning = (0, manager_1.isNativeWorkspaceRunning)(id);
209
+ if (isRunning) {
210
  const port = (0, manager_1.getNativeWorkspacePort)(id) || 8080;
211
  req.headers['x-codeverse-id'] = id;
212
  req.headers['x-codeverse-type'] = 'workspace';
 
219
  return proxy.web(req, res, { target: `http://127.0.0.1:${port}`, changeOrigin: true });
220
  }
221
  else if (!(pathname === null || pathname === void 0 ? void 0 : pathname.startsWith("/api/"))) {
222
+ // ๐Ÿš‘ WORKSPACE OFFLINE OR BOOTING: Detect if it's truly gone or just waking up
223
  res.writeHead(503, { 'Content-Type': 'text/html', 'Retry-After': '5' });
224
  return res.end(`
225
  <html>
226
  <head>
227
+ <title>CodeVerse | Workspace Status</title>
228
  <style>
229
  body { background: #09090b; color: #71717a; font-family: sans-serif; display: flex; align-items: center; justify-content: center; height: 100vh; margin: 0; }
230
+ .container { text-align: center; border: 1px solid #27272a; padding: 2.5rem; border-radius: 1rem; background: #111113; max-width: 450px; }
231
+ .spinner { width: 30px; height: 30px; border: 2px solid #3f3f46; border-top-color: #3b82f6; border-radius: 50%; animation: spin 1s linear infinite; margin: 0 auto 1.5rem; }
232
+ h1 { color: #f4f4f5; font-size: 1.25rem; margin: 0 0 1rem; }
233
+ p { font-size: 0.9rem; margin-bottom: 2rem; line-height: 1.6; }
234
+ .btn { background: #3b82f6; color: white; padding: 0.75rem 1.5rem; border-radius: 0.5rem; text-decoration: none; font-weight: bold; display: inline-block; transition: background 0.2s; }
235
+ .btn:hover { background: #2563eb; }
236
  @keyframes spin { to { transform: rotate(360deg); } }
237
  </style>
238
+ <script>setTimeout(() => window.location.reload(), 5000);</script>
239
  </head>
240
  <body>
241
  <div class="container">
242
  <div class="spinner"></div>
243
+ <h1>Provisioning Environment</h1>
244
+ <p>We're restoring your workspace from cold storage. This usually takes 30-60 seconds depending on the Nix profile complexity.</p>
245
+ <a href="/" class="btn">Return to Dashboard</a>
246
  </div>
247
  </body>
248
  </html>
 
254
  });
255
  // Setup Sockets
256
  const io = new socket_io_1.Server(server, { path: "/api/socketio" });
257
+ const shoket = new ws_1.WebSocketServer({ noServer: true });
258
  // Start Listening Immediately
259
  server.listen(PORT, HOST, () => {
260
  console.log('----------------------------------------------------');
 
291
  server.on("upgrade", (req, socket, head) => {
292
  const { pathname } = new URL(req.url || "/", `http://${req.headers.host}`);
293
  if (pathname === "/api/collab") {
294
+ shoket.handleUpgrade(req, socket, head, (ws) => {
295
+ shoket.emit("connection", ws, req);
296
  });
297
  }
298
  });
299
+ shoket.on("connection", (conn, request) => {
300
+ const { doc } = getOrCreateDoc(new URL(request.url || "/", "http://l").searchParams.get('doc') || "default");
301
  conn.binaryType = "arraybuffer";
302
  const encoder = encoding.createEncoder();
303
  encoding.writeVarUint(encoder, 0);
lib/docker/manager.ts CHANGED
@@ -39,6 +39,7 @@ export const pendingProvisioning = new Map<string, Promise<WorkspaceOperationRes
39
  */
40
  export function isNativeWorkspaceRunning(id: string): boolean {
41
  if (nativeProcesses.has(id)) return true;
 
42
  // Prefix fallback for reconnected sessions
43
  return Array.from(nativeProcesses.keys()).some(k => id.startsWith(k));
44
  }
@@ -204,7 +205,15 @@ async function performProvisioning(config: WorkspaceConfig): Promise<WorkspaceOp
204
  log(`Allocated isolated filesystem segment: ${config.id.slice(0, 8)}`);
205
  }
206
 
207
- // 2. IDX Engine: Sync Environment
 
 
 
 
 
 
 
 
208
  const idxConfig = IdxEngine.getIdxConfig(workspacePath);
209
  log(`Declarative config detected (Packages: ${idxConfig.packages.length}). Initializing synchronization...`);
210
 
@@ -219,9 +228,6 @@ async function performProvisioning(config: WorkspaceConfig): Promise<WorkspaceOp
219
  fs.writeFileSync(flagPath, new Date().toISOString());
220
  }
221
 
222
- // 4. Identify Target Port
223
- const port = findAvailablePort();
224
-
225
  // 5. Spawn code-server
226
  const shellCommand = process.platform === 'win32' ? 'npx' : 'code-server';
227
  const args = process.platform === 'win32' ? ['code-server'] : [];
@@ -261,9 +267,10 @@ async function performProvisioning(config: WorkspaceConfig): Promise<WorkspaceOp
261
 
262
  child.on('close', (code, signal) => {
263
  log(`[IDE:EXIT] IDE process died with code ${code} (Signal: ${signal})`);
 
264
  });
265
 
266
- // 6. Register in active pool
267
  nativeProcesses.set(config.id, { pid: child.pid!, port, process: child });
268
 
269
  // 7. Handshake Loop
 
39
  */
40
  export function isNativeWorkspaceRunning(id: string): boolean {
41
  if (nativeProcesses.has(id)) return true;
42
+ if (pendingProvisioning.has(id)) return true;
43
  // Prefix fallback for reconnected sessions
44
  return Array.from(nativeProcesses.keys()).some(k => id.startsWith(k));
45
  }
 
205
  log(`Allocated isolated filesystem segment: ${config.id.slice(0, 8)}`);
206
  }
207
 
208
+ // 2. Register in active pool (EARLY REGISTRATION: satisfy proxy health checks)
209
+ const port = findAvailablePort();
210
+ nativeProcesses.set(config.id, {
211
+ pid: -1, // PID not yet available
212
+ port,
213
+ process: { kill: () => true, pid: -1 } as unknown as WorkspaceProcess
214
+ });
215
+
216
+ // 3. IDX Engine: Sync Environment
217
  const idxConfig = IdxEngine.getIdxConfig(workspacePath);
218
  log(`Declarative config detected (Packages: ${idxConfig.packages.length}). Initializing synchronization...`);
219
 
 
228
  fs.writeFileSync(flagPath, new Date().toISOString());
229
  }
230
 
 
 
 
231
  // 5. Spawn code-server
232
  const shellCommand = process.platform === 'win32' ? 'npx' : 'code-server';
233
  const args = process.platform === 'win32' ? ['code-server'] : [];
 
267
 
268
  child.on('close', (code, signal) => {
269
  log(`[IDE:EXIT] IDE process died with code ${code} (Signal: ${signal})`);
270
+ nativeProcesses.delete(config.id);
271
  });
272
 
273
+ // 6. Update Registry with real Process
274
  nativeProcesses.set(config.id, { pid: child.pid!, port, process: child });
275
 
276
  // 7. Handshake Loop
lib/idx/idx-engine.ts CHANGED
@@ -164,7 +164,8 @@ export class IdxEngine {
164
  ...process.env,
165
  HOME: workspacePath,
166
  NIX_CONFIG: 'experimental-features = nix-command flakes'
167
- }
 
168
  });
169
 
170
  child.stdout.on('data', (data) => log(data.toString().trim()));
 
164
  ...process.env,
165
  HOME: workspacePath,
166
  NIX_CONFIG: 'experimental-features = nix-command flakes'
167
+ },
168
+ timeout: 300000 // 5-minute safety timeout
169
  });
170
 
171
  child.stdout.on('data', (data) => log(data.toString().trim()));
server.ts CHANGED
@@ -185,8 +185,9 @@ const server = createServer((req: IncomingMessage, res: ServerResponse) => {
185
  const id = workspaceHostMatch ? workspaceHostMatch[1] : (pathname?.startsWith("/workspace/") ? pathname.split("/")[2] : null);
186
 
187
  if (id) {
188
- const isReady = isNativeWorkspaceRunning(id);
189
- if (isReady) {
 
190
  const port = getNativeWorkspacePort(id) || 8080;
191
  req.headers['x-codeverse-id'] = id;
192
  req.headers['x-codeverse-type'] = 'workspace';
@@ -197,26 +198,30 @@ const server = createServer((req: IncomingMessage, res: ServerResponse) => {
197
  }
198
  return proxy.web(req, res, { target: `http://127.0.0.1:${port}`, changeOrigin: true });
199
  } else if (!pathname?.startsWith("/api/")) {
200
- // ๐Ÿš‘ WORKSPACE BOOTING FALLBACK: Prevent Next.js 404 while workspace is initializing
201
  res.writeHead(503, { 'Content-Type': 'text/html', 'Retry-After': '5' });
202
  return res.end(`
203
  <html>
204
  <head>
205
- <title>CodeVerse | Booting Workspace</title>
206
  <style>
207
  body { background: #09090b; color: #71717a; font-family: sans-serif; display: flex; align-items: center; justify-content: center; height: 100vh; margin: 0; }
208
- .container { text-align: center; border: 1px solid #27272a; padding: 2rem; border-radius: 1rem; background: #111113; }
209
- .spinner { width: 40px; height: 40px; border: 3px solid #3f3f46; border-top-color: #3b82f6; border-radius: 50%; animation: spin 1s linear infinite; margin: 0 auto 1.5rem; }
210
- h1 { color: #f4f4f5; font-size: 1.25rem; }
 
 
 
211
  @keyframes spin { to { transform: rotate(360deg); } }
212
  </style>
213
- <script>setTimeout(() => window.location.reload(), 3000);</script>
214
  </head>
215
  <body>
216
  <div class="container">
217
  <div class="spinner"></div>
218
- <h1>Workspace is Booting</h1>
219
- <p>Preparing your agentic session...</p>
 
220
  </div>
221
  </body>
222
  </html>
@@ -230,7 +235,7 @@ const server = createServer((req: IncomingMessage, res: ServerResponse) => {
230
 
231
  // Setup Sockets
232
  const io = new Server(server, { path: "/api/socketio" });
233
- const yjsWss = new WebSocketServer({ noServer: true });
234
 
235
  // Start Listening Immediately
236
  server.listen(PORT, HOST, () => {
@@ -273,14 +278,14 @@ server.listen(PORT, HOST, () => {
273
  server.on("upgrade", (req, socket, head) => {
274
  const { pathname } = new URL(req.url || "/", `http://${req.headers.host}`);
275
  if (pathname === "/api/collab") {
276
- yjsWss.handleUpgrade(req, socket, head, (ws) => {
277
- yjsWss.emit("connection", ws, req);
278
  });
279
  }
280
  });
281
 
282
- yjsWss.on("connection", (conn: WebSocket, request: IncomingMessage) => {
283
- const { doc, awareness } = getOrCreateDoc(new URL(request.url || "/", "http://l").searchParams.get('doc') || "default");
284
  conn.binaryType = "arraybuffer";
285
  const encoder = encoding.createEncoder();
286
  encoding.writeVarUint(encoder, 0);
 
185
  const id = workspaceHostMatch ? workspaceHostMatch[1] : (pathname?.startsWith("/workspace/") ? pathname.split("/")[2] : null);
186
 
187
  if (id) {
188
+ const isRunning = isNativeWorkspaceRunning(id);
189
+
190
+ if (isRunning) {
191
  const port = getNativeWorkspacePort(id) || 8080;
192
  req.headers['x-codeverse-id'] = id;
193
  req.headers['x-codeverse-type'] = 'workspace';
 
198
  }
199
  return proxy.web(req, res, { target: `http://127.0.0.1:${port}`, changeOrigin: true });
200
  } else if (!pathname?.startsWith("/api/")) {
201
+ // ๐Ÿš‘ WORKSPACE OFFLINE OR BOOTING: Detect if it's truly gone or just waking up
202
  res.writeHead(503, { 'Content-Type': 'text/html', 'Retry-After': '5' });
203
  return res.end(`
204
  <html>
205
  <head>
206
+ <title>CodeVerse | Workspace Status</title>
207
  <style>
208
  body { background: #09090b; color: #71717a; font-family: sans-serif; display: flex; align-items: center; justify-content: center; height: 100vh; margin: 0; }
209
+ .container { text-align: center; border: 1px solid #27272a; padding: 2.5rem; border-radius: 1rem; background: #111113; max-width: 450px; }
210
+ .spinner { width: 30px; height: 30px; border: 2px solid #3f3f46; border-top-color: #3b82f6; border-radius: 50%; animation: spin 1s linear infinite; margin: 0 auto 1.5rem; }
211
+ h1 { color: #f4f4f5; font-size: 1.25rem; margin: 0 0 1rem; }
212
+ p { font-size: 0.9rem; margin-bottom: 2rem; line-height: 1.6; }
213
+ .btn { background: #3b82f6; color: white; padding: 0.75rem 1.5rem; border-radius: 0.5rem; text-decoration: none; font-weight: bold; display: inline-block; transition: background 0.2s; }
214
+ .btn:hover { background: #2563eb; }
215
  @keyframes spin { to { transform: rotate(360deg); } }
216
  </style>
217
+ <script>setTimeout(() => window.location.reload(), 5000);</script>
218
  </head>
219
  <body>
220
  <div class="container">
221
  <div class="spinner"></div>
222
+ <h1>Provisioning Environment</h1>
223
+ <p>We're restoring your workspace from cold storage. This usually takes 30-60 seconds depending on the Nix profile complexity.</p>
224
+ <a href="/" class="btn">Return to Dashboard</a>
225
  </div>
226
  </body>
227
  </html>
 
235
 
236
  // Setup Sockets
237
  const io = new Server(server, { path: "/api/socketio" });
238
+ const shoket = new WebSocketServer({ noServer: true });
239
 
240
  // Start Listening Immediately
241
  server.listen(PORT, HOST, () => {
 
278
  server.on("upgrade", (req, socket, head) => {
279
  const { pathname } = new URL(req.url || "/", `http://${req.headers.host}`);
280
  if (pathname === "/api/collab") {
281
+ shoket.handleUpgrade(req, socket, head, (ws) => {
282
+ shoket.emit("connection", ws, req);
283
  });
284
  }
285
  });
286
 
287
+ shoket.on("connection", (conn: WebSocket, request: IncomingMessage) => {
288
+ const { doc } = getOrCreateDoc(new URL(request.url || "/", "http://l").searchParams.get('doc') || "default");
289
  conn.binaryType = "arraybuffer";
290
  const encoder = encoding.createEncoder();
291
  encoding.writeVarUint(encoder, 0);